diff --git a/inputremapper/groups.py b/inputremapper/groups.py index 58fef6a5..615f1e48 100644 --- a/inputremapper/groups.py +++ b/inputremapper/groups.py @@ -221,8 +221,6 @@ def get_unique_key(device: InputDevice): # - device.phys is empty sometimes and varies across virtual # subdevices # - device.version varies across subdevices - # - device.uniq is empty most of the time, I don't know what this is - # supposed to be return ( # device.info bustype, vendor and product are unique for # a product, but multiple similar device models would be grouped @@ -230,6 +228,9 @@ def get_unique_key(device: InputDevice): f"{device.info.bustype}_" f"{device.info.vendor}_" f"{device.info.product}_" + # device.uniq is empty most of the time. It seems to be the only way to + # distinguish multiple connected bluetooth gamepads + f"{device.uniq}_" # deivce.phys if "/input..." is removed from it, because the first # chunk seems to be unique per hardware (if it's not completely empty) f'{device.phys.split("/")[0] or "-"}' diff --git a/inputremapper/gui/autocompletion.py b/inputremapper/gui/autocompletion.py index 6e8a071a..6d775ce6 100644 --- a/inputremapper/gui/autocompletion.py +++ b/inputremapper/gui/autocompletion.py @@ -75,6 +75,7 @@ def get_incomplete_function_name(iter_: Gtk.TextIter) -> str: # bar(KEY_A,\nfoo # foo match = re.match(rf"(?:{FUNCTION_CHAIN}|{PARAMETER}|^)(\w+)$", left_text) + logger.debug('get_incomplete_function_name text: "%s" match: %s', left_text, match) if match is None: return "" diff --git a/inputremapper/gui/components/editor.py b/inputremapper/gui/components/editor.py index fc50f4d2..332eab84 100644 --- a/inputremapper/gui/components/editor.py +++ b/inputremapper/gui/components/editor.py @@ -420,14 +420,15 @@ class CodeEditor: # actually looking at the snapshot preview! In glades editor this didn't have an # effect. self.gui.set_resize_mode(Gtk.ResizeMode.IMMEDIATE) + # Syntax Highlighting + # TODO there are some similarities with python, but overall it's quite useless. + # commented out until there is proper highlighting for input-remappers syntax. # Thanks to https://github.com/wolfthefallen/py-GtkSourceCompletion-example # language_manager = GtkSource.LanguageManager() # fun fact: without saving LanguageManager into its own variable it doesn't work # python = language_manager.get_language("python") # source_view.get_buffer().set_language(python) - # TODO there are some similarities with python, but overall it's quite useless. - # commented out until there is proper highlighting for input-remappers syntax. self._update_placeholder() diff --git a/readme/development.md b/readme/development.md index 53e70ac2..6f32c0b7 100644 --- a/readme/development.md +++ b/readme/development.md @@ -27,7 +27,8 @@ Automated tests --------------- ```bash -pip install coverage --user # https://github.com/nedbat/coveragepy +pip install coverage psutil --user # https://github.com/nedbat/coveragepy, https://github.com/giampaolo/psutil +export PATH="$PATH:$HOME/.local/bin" sudo pkill -f input-remapper sudo pip install . && coverage run tests/test.py coverage combine && coverage report -m diff --git a/tests/integration/test_gui.py b/tests/integration/test_gui.py index 95f27374..318364f1 100644 --- a/tests/integration/test_gui.py +++ b/tests/integration/test_gui.py @@ -21,15 +21,18 @@ import asyncio # the tests file needs to be imported first to make sure patches are loaded +from tests.test import get_project_root + from contextlib import contextmanager from typing import Tuple, List, Optional, Iterable -from inputremapper.gui.autocompletion import get_incomplete_parameter, _get_left_text +from inputremapper.gui.autocompletion import ( + get_incomplete_parameter, + get_incomplete_function_name, +) -from inputremapper.injection.global_uinputs import global_uinputs from tests.lib.global_uinputs import reset_global_uinputs_for_service -from tests.test import get_project_root -from tests.lib.cleanup import cleanup, quick_cleanup +from tests.lib.cleanup import cleanup from tests.lib.stuff import spy from tests.lib.constants import EVENT_READ_TIMEOUT from tests.lib.fixtures import prepare_presets @@ -78,7 +81,11 @@ from inputremapper.gui.messages.message_broker import ( MessageType, ) from inputremapper.gui.messages.message_data import StatusData, CombinationRecorded -from inputremapper.gui.components.editor import MappingSelectionLabel, SET_KEY_FIRST +from inputremapper.gui.components.editor import ( + MappingSelectionLabel, + SET_KEY_FIRST, + CodeEditor, +) from inputremapper.gui.components.device_groups import DeviceGroupEntry from inputremapper.gui.controller import Controller from inputremapper.gui.reader_service import ReaderService @@ -162,6 +169,7 @@ def patch_launch(): def clean_up_integration(test): + logger.info("clean_up_integration") test.controller.stop_injecting() gtk_iteration() test.user_interface.on_gtk_close() @@ -400,6 +408,17 @@ class GuiTestBase(unittest.TestCase): self.throttle(20) + def focus_source_view(self): + # despite the focus and gtk_iterations, gtk never runs the event handlers for + # the focus-in-event (_update_placeholder), which would clear the placeholder + # text. Remove it manually, it can't be helped. Fun fact: when the + # window gets destroyed, gtk runs the handler 10 times for good measure. + # Lost one hour of my life on GTK again. It's gone! Forever! Use qt next time. + source_view = self.code_editor + self.set_focus(source_view) + self.code_editor.get_buffer().set_text("") + return source_view + def get_selection_labels(self) -> List[MappingSelectionLabel]: return self.selection_label_listbox.get_children() @@ -2003,7 +2022,9 @@ class TestGui(GuiTestBase): ) self.throttle(100) # give time for the input to arrive - self.assertEqual(self.get_unfiltered_symbol_input_text(), "") + self.assertEqual( + self.get_unfiltered_symbol_input_text(), CodeEditor.placeholder + ) self.assertTrue(self.output_box.get_sensitive()) # disable it by deleting the mapping @@ -2041,11 +2062,28 @@ class TestAutocompletion(GuiTestBase): test("foo", "foo") test("bar + foo", "foo") + def test_get_incomplete_function_name(self): + def test(text, expected): + text_view = Gtk.TextView() + Gtk.TextView.do_insert_at_cursor(text_view, text) + text_iter = text_view.get_iter_at_location(0, 0)[1] + text_iter.set_offset(len(text)) + self.assertEqual(get_incomplete_function_name(text_iter), expected) + + test("bar().foo", "foo") + test("bar()\n.foo", "foo") + test("bar().\nfoo", "foo") + test("bar(\nfoo", "foo") + test("bar(\nqux=foo", "foo") + test("bar(KEY_A,\nfoo", "foo") + test("foo", "foo") + def test_autocomplete_names(self): autocompletion = self.user_interface.autocompletion def setup(text): self.set_focus(self.code_editor) + self.code_editor.get_buffer().set_text("") Gtk.TextView.do_insert_at_cursor(self.code_editor, text) self.throttle(200) text_iter = self.code_editor.get_iter_at_location(0, 0)[1] @@ -2064,6 +2102,7 @@ class TestAutocompletion(GuiTestBase): gtk_iteration() self.set_focus(self.code_editor) + self.code_editor.get_buffer().set_text("") complete_key_name = "Test_Foo_Bar" @@ -2111,8 +2150,7 @@ class TestAutocompletion(GuiTestBase): self.controller.update_mapping(output_symbol="") gtk_iteration() - source_view = self.code_editor - self.set_focus(source_view) + source_view = self.focus_source_view() incomplete = "key(KEY_A).\nepea" Gtk.TextView.do_insert_at_cursor(source_view, incomplete) @@ -2134,8 +2172,7 @@ class TestAutocompletion(GuiTestBase): self.controller.update_mapping(output_symbol="") gtk_iteration() - source_view = self.code_editor - self.set_focus(source_view) + source_view = self.focus_source_view() Gtk.TextView.do_insert_at_cursor(source_view, "KEY_") @@ -2156,8 +2193,7 @@ class TestAutocompletion(GuiTestBase): def test_writing_still_works(self): self.controller.update_mapping(output_symbol="") gtk_iteration() - source_view = self.code_editor - self.set_focus(source_view) + source_view = self.focus_source_view() Gtk.TextView.do_insert_at_cursor(source_view, "KEY_") @@ -2186,8 +2222,7 @@ class TestAutocompletion(GuiTestBase): def test_cycling(self): self.controller.update_mapping(output_symbol="") gtk_iteration() - source_view = self.code_editor - self.set_focus(source_view) + source_view = self.focus_source_view() Gtk.TextView.do_insert_at_cursor(source_view, "KEY_") diff --git a/tests/lib/fixtures.py b/tests/lib/fixtures.py index a764731a..13d5198d 100644 --- a/tests/lib/fixtures.py +++ b/tests/lib/fixtures.py @@ -47,6 +47,9 @@ class Fixture: phys: str = "unset" group_key: Optional[str] = None + # uniq is typically empty + uniq: str = "" + def __hash__(self): return hash(self.path) diff --git a/tests/lib/patches.py b/tests/lib/patches.py index 7f317152..4213de69 100644 --- a/tests/lib/patches.py +++ b/tests/lib/patches.py @@ -74,6 +74,7 @@ class InputDevice: self.phys = self._fixture.phys self.info = self._fixture.info self.name = self._fixture.name + self.uniq = self._fixture.uniq # this property exists only for test purposes and is not part of # the original evdev.InputDevice class