diff --git a/datashuttle/tui/css/tui_tab.tcss b/datashuttle/tui/css/tui_tab.tcss index bfbfe3a96..34321a84c 100644 --- a/datashuttle/tui/css/tui_tab.tcss +++ b/datashuttle/tui/css/tui_tab.tcss @@ -32,6 +32,7 @@ TabScreen > TabbedContent { #create_folders_buttons_horizontal { height: 3; + margin: 0 0 1 0 } #template_settings_validation_on_checkbox.-on > .toggle--button{ @@ -52,9 +53,6 @@ TabScreen > TabbedContent { layout: horizontal; background: transparent; margin: 1 0 0 0; -} - -#transfer_radioset:focus { border: transparent; } diff --git a/datashuttle/tui/screens/datatypes.py b/datashuttle/tui/screens/datatypes.py index 790cca553..a7d52fbea 100644 --- a/datashuttle/tui/screens/datatypes.py +++ b/datashuttle/tui/screens/datatypes.py @@ -23,6 +23,8 @@ ) from textual.widgets.selection_list import Selection +from datashuttle.tui.utils.tui_helpers import process_str_for_textual + # -------------------------------------------------------------------------------------- # Select Displayed Datatypes Screen # -------------------------------------------------------------------------------------- @@ -261,7 +263,7 @@ def on_mount(self) -> None: if self.datatype_config[datatype]["displayed"]: self.query_one( f"#{get_checkbox_name(self.create_or_transfer, datatype)}" - ).tooltip = tooltips[datatype] + ).tooltip = process_str_for_textual(tooltips[datatype]) def selected_datatypes(self) -> List[str]: """ diff --git a/datashuttle/tui/screens/setup_ssh.py b/datashuttle/tui/screens/setup_ssh.py index 296865f2a..9219e3554 100644 --- a/datashuttle/tui/screens/setup_ssh.py +++ b/datashuttle/tui/screens/setup_ssh.py @@ -16,6 +16,8 @@ Static, ) +from datashuttle.tui.utils.tui_helpers import process_str_for_textual + class SetupSshScreen(ModalScreen): """ @@ -112,7 +114,9 @@ def ask_user_to_accept_hostkeys(self) -> None: ) self.query_one("#setup_ssh_ok_button").disabled = True - self.query_one("#messagebox_message_label").update(message) + self.query_one("#messagebox_message_label").update( + process_str_for_textual(message) + ) self.stage += 1 def save_hostkeys_and_prompt_password_input(self) -> None: @@ -137,7 +141,9 @@ def save_hostkeys_and_prompt_password_input(self) -> None: ) self.query_one("#setup_ssh_ok_button").disabled = True - self.query_one("#messagebox_message_label").update(message) + self.query_one("#messagebox_message_label").update( + process_str_for_textual(message) + ) self.stage += 1 def use_password_to_setup_ssh_key_pairs(self) -> None: @@ -169,4 +175,6 @@ def use_password_to_setup_ssh_key_pairs(self) -> None: ) self.failed_password_attempts += 1 - self.query_one("#messagebox_message_label").update(message) + self.query_one("#messagebox_message_label").update( + process_str_for_textual(message) + ) diff --git a/datashuttle/tui/tabs/create_folders.py b/datashuttle/tui/tabs/create_folders.py index bbbc50343..27eb2f61a 100644 --- a/datashuttle/tui/tabs/create_folders.py +++ b/datashuttle/tui/tabs/create_folders.py @@ -217,7 +217,7 @@ def revalidate_inputs(self, all_prefixes: List[str]) -> None: value = self.query_one(key).value self.query_one(key).validate(value=value) - def update_input_tooltip(self, message: List[str], prefix: Prefix) -> None: + def update_input_tooltip(self, message: str, prefix: Prefix) -> None: """ Update the value of a subject or session tooltip, which indicates the validation status of the input value. diff --git a/datashuttle/tui/tabs/logging.py b/datashuttle/tui/tabs/logging.py index fb45c8269..3667d8322 100644 --- a/datashuttle/tui/tabs/logging.py +++ b/datashuttle/tui/tabs/logging.py @@ -10,6 +10,7 @@ CustomDirectoryTree, ) from datashuttle.tui.utils.tui_decorators import require_double_click +from datashuttle.tui.utils.tui_helpers import process_str_for_textual class RichLogScreen(ModalScreen): @@ -85,7 +86,9 @@ def _on_mount(self, event: events.Mount) -> None: def update_most_recent_label(self): self.update_latest_log_path() self.query_one("#logging_most_recent_label").update( - f"or open most recent: {self.latest_log_path.stem}" + process_str_for_textual( + f"or open most recent: {self.latest_log_path.stem}" + ) ) self.refresh() diff --git a/datashuttle/tui/tooltips.py b/datashuttle/tui/tooltips.py index e8d0c771b..2cf6a6c28 100644 --- a/datashuttle/tui/tooltips.py +++ b/datashuttle/tui/tooltips.py @@ -1,3 +1,6 @@ +from datashuttle.tui.utils.tui_helpers import process_str_for_textual + + def get_tooltip(id: str) -> str: """ Master function to get tooltips for all widgets, @@ -230,4 +233,4 @@ def get_tooltip(id: str) -> str: "NeuroBlueprint-formatted sub-, ses- or datatype folder." ) - return tooltip + return process_str_for_textual(tooltip) diff --git a/datashuttle/tui/utils/tui_helpers.py b/datashuttle/tui/utils/tui_helpers.py new file mode 100644 index 000000000..2ddd133ce --- /dev/null +++ b/datashuttle/tui/utils/tui_helpers.py @@ -0,0 +1,6 @@ +def process_str_for_textual(message: str) -> str: + """ + From textual v2, "[" in string is interpreted as markdown style syntax. + These need to be escaped in all strings passed to textual. + """ + return message.replace("[", "\[") diff --git a/datashuttle/tui/utils/tui_validators.py b/datashuttle/tui/utils/tui_validators.py index 792fc1c5e..057dc9523 100644 --- a/datashuttle/tui/utils/tui_validators.py +++ b/datashuttle/tui/utils/tui_validators.py @@ -12,6 +12,8 @@ from textual.validation import ValidationResult, Validator +from datashuttle.tui.utils.tui_helpers import process_str_for_textual + class NeuroBlueprintValidator(Validator): def __init__(self, prefix: Prefix, parent: CreateFoldersTab) -> None: @@ -33,7 +35,9 @@ def validate(self, name: str) -> ValidationResult: """ valid, message = self.parent.run_local_validation(self.prefix) - self.parent.update_input_tooltip(message, self.prefix) + self.parent.update_input_tooltip( + process_str_for_textual(message), self.prefix + ) if valid: if self.prefix == "sub": diff --git a/pyproject.toml b/pyproject.toml index 997f6f406..9514a01ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "fancylog>=0.4.2", "simplejson", "pyperclip", - "textual<=1.0.0", + "textual<=3.3.0", "show-in-file-manager", "gitpython", "typeguard" diff --git a/tests/tests_tui/test_tui_create_folders.py b/tests/tests_tui/test_tui_create_folders.py index 00a408f76..32bcf33ab 100644 --- a/tests/tests_tui/test_tui_create_folders.py +++ b/tests/tests_tui/test_tui_create_folders.py @@ -1,5 +1,3 @@ -import re - import pytest import test_utils from tui_base import TuiBase @@ -98,6 +96,7 @@ async def test_create_folders_formatted_names(self, setup_project_paths): await self.check_and_click_onto_existing_project( pilot, project_name ) + import re # Fill the subject input with names with @DATE@ tags and # check the tooltip displays the formatted value. @@ -107,15 +106,19 @@ async def test_create_folders_formatted_names(self, setup_project_paths): "sub-001_@DATE@, sub-002_@DATE@", ) - sub_1_regexp = "sub\-001_date\-\d{8}" - sub_2_regexp = "sub\-002_date\-\d{8}" + # A lot of regexp magic required to dynamically + # process date str but handle the [ escape needed + # to avoid textual processing these as markdown style syntax + sub_1_regexp = r"sub-001_date-\d{8}" + sub_2_regexp = r"sub-002_date-\d{8}" sub_tooltip_regexp = ( - "Formatted names: \['" + re.escape("Formatted names: \\['") + sub_1_regexp - + "', '" + + re.escape("', '") + sub_2_regexp - + "'\]" + + re.escape("']") ) + sub_tooltip = pilot.app.screen.query_one( "#create_folders_subject_input" ).tooltip @@ -127,17 +130,19 @@ async def test_create_folders_formatted_names(self, setup_project_paths): pilot, "#create_folders_session_input", "ses-001@TO@003_@DATE@" ) - ses_1_regexp = "ses\-001_date\-\d{8}" - ses_2_regexp = "ses\-002_date\-\d{8}" - ses_3_regexp = "ses\-003_date\-\d{8}" + ses_1_regexp = r"ses-001_date-\d{8}" + ses_2_regexp = r"ses-002_date-\d{8}" + ses_3_regexp = r"ses-003_date-\d{8}" + + # Build pattern with escaped fixed parts, regex for date parts ses_tooltip_regexp = ( - "Formatted names: \['" + re.escape("Formatted names: \\['") + ses_1_regexp - + "', '" + + re.escape("', '") + ses_2_regexp - + "', '" + + re.escape("', '") + ses_3_regexp - + "'\]" + + re.escape("']") ) ses_tooltip = pilot.app.screen.query_one( "#create_folders_session_input" @@ -225,7 +230,7 @@ async def test_create_folders_bad_validation_tooltips( pilot.app.screen.query_one( "#create_folders_subject_input" ).tooltip - == "Formatted names: ['sub-001']" + == "Formatted names: \['sub-001']" ) assert ( pilot.app.screen.query_one( @@ -411,8 +416,9 @@ async def test_name_template_next_sub_or_ses_and_validation( pilot.app.screen.query_one( "#create_folders_subject_input" ).tooltip - == "Formatted names: ['sub-001']" + == "Formatted names: \['sub-001']" ) + assert ( pilot.app.screen.query_one( "#create_folders_session_input"