diff --git a/poetry.lock b/poetry.lock index 718296f..f2f59a5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2424,4 +2424,4 @@ watchmedo = ["PyYAML (>=3.10)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "96afc722df2e1cdb0f1294ebc86f88e881dce618e788ed799acc210ccb07ef2a" +content-hash = "244156c52b101f607c0e6c6ae14a61a39f59aa61f6a705a39741c658beb3c48d" diff --git a/pyproject.toml b/pyproject.toml index eabc4b6..ce60304 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ mkdocstrings = { extras = ["python"], version = "^0.20.0" } # textual = "^0.51.0" textual = { extras = ["syntax"], version = "^0.51.0" } textual-plotext = "^0.2.1" +tree-sitter-languages = "==1.10.2" # Pin for security, to avoid re-auditing [tool.poetry.group.plot.dependencies] matplotlib = "^3.8.3" diff --git a/src/hpc_multibench/tui/bash_highlights.py b/src/hpc_multibench/tui/bash_highlights.py new file mode 100755 index 0000000..8dae164 --- /dev/null +++ b/src/hpc_multibench/tui/bash_highlights.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +A tree-sitter query for highlighting bash. + +Query string derived from: +https://github.com/nvim-treesitter/nvim-treesitter/blob/f95ffd09ed35880c3a46ad2b968df361fa592a76/queries/bash/highlights.scm + +Which is aligned with the approach taken for Textual builtin languages, for example: +https://github.com/Textualize/textual/blob/main/src/textual/tree-sitter/highlights/python.scm +""" + +BASH_HIGHLIGHTS = """ +(simple_expansion) @none +(expansion + "${" @punctuation.special + "}" @punctuation.special) @none +[ + "(" + ")" + "((" + "))" + "{" + "}" + "[" + "]" + "[[" + "]]" + ] @punctuation.bracket + +[ + ";" + ";;" + (heredoc_start) + ] @punctuation.delimiter + +[ + "$" +] @punctuation.special + +[ + ">" + ">>" + "<" + "<<" + "&" + "&&" + "|" + "||" + "=" + "=~" + "==" + "!=" + ] @operator + +[ + (string) + (raw_string) + (ansi_c_string) + (heredoc_body) +] @string @spell + +(variable_assignment (word) @string) + +[ + "if" + "then" + "else" + "elif" + "fi" + "case" + "in" + "esac" + ] @conditional + +[ + "for" + "do" + "done" + "while" + ] @repeat + +[ + "declare" + "export" + "local" + "readonly" + "unset" + ] @keyword + +"function" @keyword.function + +(special_variable_name) @constant + +; trap -l +((word) @constant.builtin + (#match? @constant.builtin "^SIG(HUP|INT|QUIT|ILL|TRAP|ABRT|BUS|FPE|KILL|USR[12]|SEGV|PIPE|ALRM|TERM|STKFLT|CHLD|CONT|STOP|TSTP|TT(IN|OU)|URG|XCPU|XFSZ|VTALRM|PROF|WINCH|IO|PWR|SYS|RTMIN([+]([1-9]|1[0-5]))?|RTMAX(-([1-9]|1[0-4]))?)$")) + +((word) @boolean + (#match? @boolean "^(true|false)$")) + +(comment) @comment @spell +(test_operator) @string + +(command_substitution + [ "$(" ")" ] @punctuation.bracket) + +(process_substitution + [ "<(" ")" ] @punctuation.bracket) + + +(function_definition + name: (word) @function) + +(command_name (word) @function.call) + +((command_name (word) @function.builtin) + (#any-of? @function.builtin + "alias" "cd" "clear" "echo" "eval" "exit" "getopts" "popd" + "pushd" "return" "set" "shift" "shopt" "source" "test")) + +(command + argument: [ + (word) @parameter + (concatenation (word) @parameter) + ]) + +((word) @number + (#lua-match? @number "^[0-9]+$")) + +(file_redirect + descriptor: (file_descriptor) @operator + destination: (word) @parameter) + +(expansion + [ "${" "}" ] @punctuation.bracket) + +(variable_name) @variable + +((variable_name) @constant + (#lua-match? @constant "^[A-Z][A-Z_0-9]*$")) + +(case_item + value: (word) @parameter) + +(regex) @string.regex + +((program . (comment) @preproc) + (#match? @preproc "^#!/")) +""" diff --git a/src/hpc_multibench/tui/interactive_ui.py b/src/hpc_multibench/tui/interactive_ui.py index 514b123..8f73155 100755 --- a/src/hpc_multibench/tui/interactive_ui.py +++ b/src/hpc_multibench/tui/interactive_ui.py @@ -24,11 +24,13 @@ ) from textual.widgets.tree import TreeNode from textual_plotext import PlotextPlot +from tree_sitter_languages import get_language from hpc_multibench.plot import plot_matplotlib, plot_plotext from hpc_multibench.run_configuration import RunConfiguration, get_queued_job_ids from hpc_multibench.test_bench import TestBench from hpc_multibench.test_plan import TestPlan +from hpc_multibench.tui.bash_highlights import BASH_HIGHLIGHTS from hpc_multibench.uncertainties import UFloat from hpc_multibench.yaml_model import ( BarChartModel, @@ -210,6 +212,7 @@ def compose(self) -> ComposeResult: id="sbatch-contents", read_only=True, show_line_numbers=True, + theme="monokai", ) with TabPane("Metrics", id="metrics-tab"): yield DataTable(id="metrics-table") @@ -220,6 +223,10 @@ def compose(self) -> ComposeResult: def on_mount(self) -> None: """Initialise data when the application is created.""" self.initialise_test_plan_tree() + self.query_one("#sbatch-contents", TextArea).register_language( + get_language("bash"), BASH_HIGHLIGHTS + ) + self.query_one("#sbatch-contents", TextArea).language = "bash" def on_button_pressed(self, event: Button.Pressed) -> None: """When a button is pressed.""" @@ -397,7 +404,7 @@ def get_plot_model( def action_reload_test_plan(self) -> None: """Reload the test plan for the user interface.""" self.test_plan = TestPlan(self.test_plan.yaml_path) - self.on_mount() + self.initialise_test_plan_tree() self.update_all_tabs() def action_change_plot(self, offset: int) -> None: