From 0796ecfc580fb24959b8d0b357db5fe2ffda8c9c Mon Sep 17 00:00:00 2001 From: HelioGuilherme66 Date: Wed, 25 Jun 2025 00:40:51 +0100 Subject: [PATCH 01/17] Change some imports to use installed robot --- src/robotide/lib/robot/libdocpkg/htmlwriter.py | 8 ++++++-- src/robotide/spec/iteminfo.py | 9 ++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/robotide/lib/robot/libdocpkg/htmlwriter.py b/src/robotide/lib/robot/libdocpkg/htmlwriter.py index dacd4d706..5274c0c86 100644 --- a/src/robotide/lib/robot/libdocpkg/htmlwriter.py +++ b/src/robotide/lib/robot/libdocpkg/htmlwriter.py @@ -20,8 +20,12 @@ from urllib.parse import quote from robotide.lib.robot.errors import DataError -from robotide.lib.robot.htmldata import HtmlFileWriter, ModelWriter, JsonWriter, LIBDOC -from robotide.lib.robot.utils import get_timestamp, html_escape, html_format, NormalizedDict +try: + from robot.htmldata import HtmlFileWriter, ModelWriter, JsonWriter, LIBDOC + from robot.utils import get_timestamp, html_escape, html_format, NormalizedDict +except (ImportError, ModuleNotFoundError): + from robotide.lib.robot.htmldata import HtmlFileWriter, ModelWriter, JsonWriter, LIBDOC + from robotide.lib.robot.utils import get_timestamp, html_escape, html_format, NormalizedDict from robotide.lib.robot.utils.htmlformatters import HeaderFormatter diff --git a/src/robotide/spec/iteminfo.py b/src/robotide/spec/iteminfo.py index 771a04c76..15e333467 100644 --- a/src/robotide/spec/iteminfo.py +++ b/src/robotide/spec/iteminfo.py @@ -17,7 +17,10 @@ from functools import total_ordering from .. import utils -from ..lib.robot.libdocpkg.htmlwriter import DocToHtml +try: + from robot.utils import html_format +except (ImportError, ModuleNotFoundError): + from robotide.lib.robot.utils import html_format class ItemInfo(object): @@ -180,7 +183,7 @@ def arguments(self): @property def details(self): - formatter = DocToHtml(self.doc_format) + # formatter = html_format(self.doc_format) return ('' '' '' @@ -190,7 +193,7 @@ def details(self): '' '
Name:%s
Source:%s <%s>
%s
') % (self._name(self.item), self._source(self.item), self._type, self._format_args(self.arguments), - formatter(self.doc)) + html_format(self.doc)) @staticmethod def _format_args(args): From 00a8a30eb6d7cbcc799e2430e7af971d9b389db6 Mon Sep 17 00:00:00 2001 From: HelioGuilherme66 Date: Sat, 28 Jun 2025 22:47:25 +0100 Subject: [PATCH 02/17] Use installed Robot Framework libraries in libdoc import modules --- .../lib/robot/running/testlibraries.py | 69 ++++++++----------- src/robotide/namespace/cache.py | 6 +- src/robotide/namespace/namespace.py | 3 - src/robotide/spec/libraryfetcher.py | 4 +- src/robotide/spec/librarymanager.py | 3 +- 5 files changed, 35 insertions(+), 50 deletions(-) diff --git a/src/robotide/lib/robot/running/testlibraries.py b/src/robotide/lib/robot/running/testlibraries.py index cd6bded0c..0c92478af 100644 --- a/src/robotide/lib/robot/running/testlibraries.py +++ b/src/robotide/lib/robot/running/testlibraries.py @@ -16,27 +16,35 @@ import inspect import os -from robotide.lib.robot.errors import DataError -from robotide.lib.robot.libraries import STDLIBS -from robotide.lib.robot.output import LOGGER -from robotide.lib.robot.utils import (getdoc, get_error_details, Importer, is_java_init, - is_java_method, JYTHON, normalize, seq2str2, unic, - is_list_like, PY2, PYPY, type_name) - -from .arguments import EmbeddedArguments -from .context import EXECUTION_CONTEXTS -from .dynamicmethods import (GetKeywordArguments, GetKeywordDocumentation, - GetKeywordNames, GetKeywordTags, RunKeyword) +try: + from robot.errors import DataError + from robot.libraries import STDLIBS + from robot.output import LOGGER + from robot.utils import (get_error_details, getdoc, Importer, is_dict_like, is_list_like, normalize, + NormalizedDict, seq2str2, type_name) + from robot.running.arguments import EmbeddedArguments + from robot.running.context import EXECUTION_CONTEXTS + from robot.running.dynamicmethods import (GetKeywordArguments, GetKeywordDocumentation, + GetKeywordNames, GetKeywordTags, RunKeyword) + from robot.running.libraryscopes import LibraryScope + from robot.running.outputcapture import OutputCapturer +except (ImportError, ModuleNotFoundError): + from robotide.lib.robot.errors import DataError + from robotide.lib.robot.libraries import STDLIBS + from robotide.lib.robot.output import LOGGER + from robotide.lib.robot.utils import (getdoc, get_error_details, Importer, normalize, seq2str2, + is_list_like, type_name) + from .arguments import EmbeddedArguments + from .context import EXECUTION_CONTEXTS + from .dynamicmethods import (GetKeywordArguments, GetKeywordDocumentation, + GetKeywordNames, GetKeywordTags, RunKeyword) + from .libraryscopes import LibraryScope + from .outputcapture import OutputCapturer + from .handlers import Handler, InitHandler, DynamicHandler, EmbeddedArgumentsHandler from .handlerstore import HandlerStore -from .libraryscopes import LibraryScope -from .outputcapture import OutputCapturer - -if JYTHON: - from java.lang import Object -else: - Object = None +Object = None def TestLibrary(name, args=None, variables=None, create_handlers=True): @@ -46,8 +54,7 @@ def TestLibrary(name, args=None, variables=None, create_handlers=True): import_name = name with OutputCapturer(library_import=True): importer = Importer('test library') - libcode, source = importer.import_class_or_module(import_name, - return_source=True) + libcode, source = importer.import_class_or_module(import_name, return_source=True) libclass = _get_lib_class(libcode) lib = libclass(libcode, name, args or [], source, variables) if create_handlers: @@ -121,7 +128,7 @@ def _get_version(self, libcode): or self._get_attr(libcode, '__version__') def _get_attr(self, object, attr, default='', upper=False): - value = unic(getattr(object, attr, default)) + value = getattr(object, attr, default) if upper: value = normalize(value, ignore='_').upper() return value @@ -137,14 +144,7 @@ def _resolve_init_method(self, libcode): return init if init and self._valid_init(init) else lambda: None def _valid_init(self, method): - # https://bitbucket.org/pypy/pypy/issues/2462/ - if PYPY: - if PY2: - return method.__func__ is not object.__init__.__func__ - return method is not object.__init__ - return (inspect.ismethod(method) or # PY2 - inspect.isfunction(method) or # PY3 - is_java_init(method)) + return inspect.isfunction(method) def reset_instance(self, instance=None): prev = self._libinst @@ -330,17 +330,6 @@ def _get_handler_method(self, libinst, name): def _validate_handler(self, handler): if not inspect.isroutine(handler): raise DataError('Not a method or function') - if self._is_implicit_java_or_jython_method(handler): - raise DataError('Implicit methods are ignored') - - def _is_implicit_java_or_jython_method(self, handler): - if not is_java_method(handler): - return False - for signature in handler.argslist[:handler.nargs]: - cls = signature.declaringClass - if not (cls is Object or cls.__module__ == 'org.python.proxies'): - return False - return True class _ModuleLibrary(_BaseTestLibrary): diff --git a/src/robotide/namespace/cache.py b/src/robotide/namespace/cache.py index e6bf5f09c..15ab098a7 100644 --- a/src/robotide/namespace/cache.py +++ b/src/robotide/namespace/cache.py @@ -59,9 +59,9 @@ def _default_kws(self): def get_all_cached_library_names(self): all_libraries = self.get_user_libraries() + [name for name, _ in self._library_keywords] - ordered = set(sorted(all_libraries)) - all_libraries = list(ordered) - # print(f"DEBUG: cache.py LibraryCache get_all_cached_library_names user_libraries={all_libraries}") + ordered = set(all_libraries) + all_libraries = sorted(ordered) + print(f"DEBUG: cache.py LibraryCache get_all_cached_library_names user_libraries={all_libraries}") return all_libraries def _get_library(self, name, args): diff --git a/src/robotide/namespace/namespace.py b/src/robotide/namespace/namespace.py index 9a4bcfa0c..503e11517 100644 --- a/src/robotide/namespace/namespace.py +++ b/src/robotide/namespace/namespace.py @@ -46,7 +46,6 @@ def __init__(self, settings): PUBLISHER.subscribe(self._setting_changed, RideSettingsChanged) def _init_caches(self): - # print(f"DEBUG: namespace.py Namespace _init_caches {self.settings}") self._lib_cache = LibraryCache( self.settings, self.update, self._library_manager) self._resource_factory = ResourceFactory(self.settings) @@ -141,7 +140,6 @@ def get_suggestions_for(self, controller, start): datafile = controller.datafile ctx = self._context_factory.ctx_for_controller(controller) sugs = set() # self._words_cache or - # print(f"DEBUG: namespace.py Namespace get_suggestions_for ENTER start={start} {datafile=} {ctx=} {sugs=}") while start and start[-1] in [']', '}', '=', ',']: start = start[:-1] sugs.update(self._get_suggestions_from_hooks(datafile, start)) @@ -159,7 +157,6 @@ def get_suggestions_for(self, controller, start): # print(f"DEBUG: namespace.py Namespace get_suggestions_for FROM CONTENT start={start} {sugs=}") sugs_list = list(sugs) sugs_list.sort() - # print(f"DEBUG: namespace.py Namespace get_suggestions_for RETURN {sugs_list=}") return sugs_list def _get_suggestions_from_hooks(self, datafile, start): diff --git a/src/robotide/spec/libraryfetcher.py b/src/robotide/spec/libraryfetcher.py index 6e4008c1a..6eaf81bf8 100644 --- a/src/robotide/spec/libraryfetcher.py +++ b/src/robotide/spec/libraryfetcher.py @@ -13,12 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .. import robotapi +from ..robotapi import TestLibrary from .iteminfo import LibraryKeywordInfo def get_import_result(path, args): - lib = robotapi.TestLibrary(path, args) + lib = TestLibrary(path, args) kws = [ LibraryKeywordInfo( kw.name, diff --git a/src/robotide/spec/librarymanager.py b/src/robotide/spec/librarymanager.py index 548b23795..d9dc1263c 100644 --- a/src/robotide/spec/librarymanager.py +++ b/src/robotide/spec/librarymanager.py @@ -146,8 +146,7 @@ def fetch_keywords(self, library_name, library_args, callback): def get_and_insert_keywords(self, library_name, library_args): result_queue = queue.Queue(maxsize=1) - self._messages.put( - ('insert', library_name, library_args, result_queue), timeout=3) + self._messages.put(('insert', library_name, library_args, result_queue), timeout=3) try: return result_queue.get(timeout=5) except queue.Empty as e: From 9535f78e3f05f682a126203e7abbb408f61fe760 Mon Sep 17 00:00:00 2001 From: HelioGuilherme66 Date: Mon, 30 Jun 2025 00:51:08 +0100 Subject: [PATCH 03/17] Add attributes and is_private for UserKeywords --- src/robotide/controller/macrocontrollers.py | 4 ++++ src/robotide/lib/robot/parsing/model.py | 5 +++++ src/robotide/lib/robot/running/model.py | 19 ++++++++++++----- src/robotide/namespace/namespace.py | 3 +++ utest/namespace/test_namespace.py | 23 ++++++++++++++++++++- 5 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/robotide/controller/macrocontrollers.py b/src/robotide/controller/macrocontrollers.py index 020b9d92a..9ccce1edd 100644 --- a/src/robotide/controller/macrocontrollers.py +++ b/src/robotide/controller/macrocontrollers.py @@ -499,6 +499,10 @@ def teardown(self): def arguments(self): return ArgumentsController(self, self.kw.args) + @property + def is_private(self): + return self.kw.is_private + def validate_keyword_name(self, name): return self._parent.validate_name(name) diff --git a/src/robotide/lib/robot/parsing/model.py b/src/robotide/lib/robot/parsing/model.py index afebd024c..2e1d76b5c 100644 --- a/src/robotide/lib/robot/parsing/model.py +++ b/src/robotide/lib/robot/parsing/model.py @@ -1129,6 +1129,11 @@ def _add_to_parent(self, test): def settings(self): return [self.args, self.doc, self.setup_, self.tags, self.timeout, self.teardown, self.return_] + @property + def is_private(self): + return (self.name.startswith('_') or + (self.tags and 'robot:private' in [x.replace(' ', '').lower() for x in self.tags])) + def __iter__(self): for element in ([self.args, self.doc, self.setup_, self.tags, self.timeout] + self.steps + [self.teardown, self.return_]): diff --git a/src/robotide/lib/robot/running/model.py b/src/robotide/lib/robot/running/model.py index d22557ac6..3e1bd2241 100644 --- a/src/robotide/lib/robot/running/model.py +++ b/src/robotide/lib/robot/running/model.py @@ -32,11 +32,16 @@ __ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#programmatic-modification-of-results __ http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#listener-interface """ - -from robotide.lib.robot import model -from robotide.lib.robot.conf import RobotSettings -from robotide.lib.robot.output import LOGGER, Output, pyloggingconf -from robotide.lib.robot.utils import setter +try: + from robot import model + from robot.conf import RobotSettings + from robot.output import LOGGER, Output, pyloggingconf + from robot.utils import setter +except (ImportError, ModuleNotFoundError): + from robotide.lib.robot import model + from robotide.lib.robot.conf import RobotSettings + from robotide.lib.robot.output import LOGGER, Output, pyloggingconf + from robotide.lib.robot.utils import setter from .steprunner import StepRunner from .randomizer import Randomizer @@ -279,6 +284,10 @@ def __init__(self, name, args=(), doc='', tags=(), return_=None, timeout=None): self.timeout = timeout self.keywords = [] + @property + def is_private(self): + return self.name.startswith('_') or 'robot:private' in [x.replace(' ', '').lower() for x in self.tags] + @setter def keywords(self, keywords): return model.Keywords(Keyword, self, keywords) diff --git a/src/robotide/namespace/namespace.py b/src/robotide/namespace/namespace.py index 503e11517..25852f466 100644 --- a/src/robotide/namespace/namespace.py +++ b/src/robotide/namespace/namespace.py @@ -235,6 +235,9 @@ def find_user_keyword(self, datafile, kw_name): kw = self.find_keyword(datafile, kw_name) return kw if isinstance(kw, UserKeywordInfo) else None + def is_private(self, datafile, kwname): # TODO: Add condition for robot:private in tags + return kwname.startswith('_') and self.is_user_keyword(datafile, kwname) + def is_user_keyword(self, datafile, kw_name): return bool(self.find_user_keyword(datafile, kw_name)) diff --git a/utest/namespace/test_namespace.py b/utest/namespace/test_namespace.py index 593f04b10..78eb2bedb 100644 --- a/utest/namespace/test_namespace.py +++ b/utest/namespace/test_namespace.py @@ -81,6 +81,11 @@ def _add_keyword_table(tcf): uk_table.keywords[0].args.value = [ '${keyword argument}', '${colliding argument}', '${keyword argument with default} = default'] + uk_table.add('My ${private} Keyword') + uk_table.keywords[1].steps = ['No Operation', 'Log | ${private}'] + uk_table.keywords[1].tags = ['robot:private'] + uk_table.add('_another private keyword') + uk_table.keywords[2].steps = ['No Operation'] class ParentMock(object): @@ -120,6 +125,7 @@ def test_get_cached_lib_names(self): def test_getting_suggestions_for_empty_datafile(self): start = 'shOulD' # print("DEBUG: %s kw %s\n" % (start, self.kw.__doc__)) + # print(f"DEBUG: TestKeywordsSuggestions keywords={[x for x in self.tcf_ctrl.keywords]}") sugs = self.ns.get_suggestions_for(self.kw, start) assert len(sugs) > 0 for s in sugs: @@ -134,6 +140,21 @@ def test_user_keywords(self): sugs = self.ns.get_suggestions_for(self.kw, 'sHoUlD') assert EXISTING_USER_KEYWORD in [s.name for s in sugs] + def test_user_embedded_arg_keywords(self): + sugs = self.ns.get_suggestions_for(self.kw, 'My') + assert 'My ${private} Keyword' in [s.name for s in sugs] + for s in sugs: + print(s) + + def test_user_private_keywords(self): + # print(f"DEBUG: TestKeywordsSuggestions test_user_private_keywords " + # f" private_keywords={[x.is_private for x in self.tcf_ctrl.keywords]}") + sugs = self.ns.get_suggestions_for(self.kw, '_another') + assert '_another private keyword' in [s.name for s in sugs] + assert [False, True, True] == [x.is_private for x in self.tcf_ctrl.keywords] + for s in sugs: + print(s) + def test_imported_lib_keywords(self): sugs = self.ns.get_suggestions_for(self.kw, 'create file') self._assert_import_kws(sugs, OS_LIB) @@ -563,7 +584,7 @@ def test_file_with_invalid_path(self): assert self._res_cache.get_resource(imp.directory, imp.name) is None if IS_WINDOWS: - def test_case_sensetive_filenames(self): + def test_case_sensitive_filenames(self): imp = Resource(None, RESOURCE_PATH) first = self._res_cache.get_resource( imp.directory, imp.name.lower()) From a572ea5089de7b1e4cede48bb324bed2637a565f Mon Sep 17 00:00:00 2001 From: HelioGuilherme66 Date: Tue, 1 Jul 2025 01:04:40 +0100 Subject: [PATCH 04/17] Show private keyword setting in ItemInfo Details --- src/robotide/controller/macrocontrollers.py | 4 ++-- src/robotide/lib/robot/parsing/model.py | 2 +- src/robotide/lib/robot/running/model.py | 2 +- src/robotide/spec/iteminfo.py | 17 ++++++++++++++++- utest/namespace/test_namespace.py | 2 +- 5 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/robotide/controller/macrocontrollers.py b/src/robotide/controller/macrocontrollers.py index 9ccce1edd..fdec071ec 100644 --- a/src/robotide/controller/macrocontrollers.py +++ b/src/robotide/controller/macrocontrollers.py @@ -500,8 +500,8 @@ def arguments(self): return ArgumentsController(self, self.kw.args) @property - def is_private(self): - return self.kw.is_private + def is_private_keyword(self): + return self.kw.is_private_keyword def validate_keyword_name(self, name): return self._parent.validate_name(name) diff --git a/src/robotide/lib/robot/parsing/model.py b/src/robotide/lib/robot/parsing/model.py index 2e1d76b5c..80d57a443 100644 --- a/src/robotide/lib/robot/parsing/model.py +++ b/src/robotide/lib/robot/parsing/model.py @@ -1130,7 +1130,7 @@ def settings(self): return [self.args, self.doc, self.setup_, self.tags, self.timeout, self.teardown, self.return_] @property - def is_private(self): + def is_private_keyword(self): return (self.name.startswith('_') or (self.tags and 'robot:private' in [x.replace(' ', '').lower() for x in self.tags])) diff --git a/src/robotide/lib/robot/running/model.py b/src/robotide/lib/robot/running/model.py index 3e1bd2241..b73f71144 100644 --- a/src/robotide/lib/robot/running/model.py +++ b/src/robotide/lib/robot/running/model.py @@ -285,7 +285,7 @@ def __init__(self, name, args=(), doc='', tags=(), return_=None, timeout=None): self.keywords = [] @property - def is_private(self): + def is_private_keyword(self): return self.name.startswith('_') or 'robot:private' in [x.replace(' ', '').lower() for x in self.tags] @setter diff --git a/src/robotide/spec/iteminfo.py b/src/robotide/spec/iteminfo.py index 15e333467..4f233a11a 100644 --- a/src/robotide/spec/iteminfo.py +++ b/src/robotide/spec/iteminfo.py @@ -25,6 +25,7 @@ class ItemInfo(object): """Represents an object that can be displayed by content assistant.""" + private = False def __init__(self, name, source, details): """Creates an item info. @@ -34,6 +35,8 @@ def __init__(self, name, source, details): Item name. Is shown in the first column of the content assist popup. source Item source. Is shown in the second column of the content assist popup. + private + Only for UserKeywords (in TestSuiteFile or ResourceFile) details Detailed information for item that is shown in the additional popup besides the list that contains content assist values. Will be @@ -62,6 +65,10 @@ def is_library_keyword(self): def is_user_keyword(self): return not self.is_library_keyword() + @property + def is_private_keyword(self): + return False + @staticmethod def m_cmp(a, b): return (a > b) - (a < b) @@ -176,6 +183,11 @@ def __init__(self, item): ItemInfo.__init__(self, self._name(item), self._source(item), None) self.shortdoc = self.doc.splitlines()[0] if self.doc else '' self.item = item + self.private = self.is_private_keyword + + @property + def is_private_keyword(self): + return self.item.name.startswith('_') or self.item.is_private_keyword @property def arguments(self): @@ -187,11 +199,13 @@ def details(self): return ('' '' '' + '%s' '' '
Name:%s
Source:%s <%s>
Arguments:%s
' '' '' '
%s
') % (self._name(self.item), self._source(self.item), self._type, + f'Private:True' if self.private else '', self._format_args(self.arguments), html_format(self.doc)) @@ -200,7 +214,8 @@ def _format_args(args): return '[ %s ]' % ' | '.join(args) def __str__(self): - return 'KeywordInfo[name: %s, source: %s, doc: %s]' % (self.name, self.source, self.doc) + return ('KeywordInfo[name: %s, source: %s, private: %s, doc: %s]' % + (self.name, self.source, self.private, self.doc)) def _name(self, item): return item.name diff --git a/utest/namespace/test_namespace.py b/utest/namespace/test_namespace.py index 78eb2bedb..106481c40 100644 --- a/utest/namespace/test_namespace.py +++ b/utest/namespace/test_namespace.py @@ -151,7 +151,7 @@ def test_user_private_keywords(self): # f" private_keywords={[x.is_private for x in self.tcf_ctrl.keywords]}") sugs = self.ns.get_suggestions_for(self.kw, '_another') assert '_another private keyword' in [s.name for s in sugs] - assert [False, True, True] == [x.is_private for x in self.tcf_ctrl.keywords] + assert [False, True, True] == [x.is_private_keyword for x in self.tcf_ctrl.keywords] for s in sugs: print(s) From 10450a4ae1a06c541f761e6def7c2b50e93e4778 Mon Sep 17 00:00:00 2001 From: HelioGuilherme66 Date: Fri, 4 Jul 2025 02:13:14 +0100 Subject: [PATCH 05/17] Sort list of suggestions, fix left, right arrows. WIP use Tab to select and edit. DEBUG --- src/robotide/editor/__init__.py | 1 + src/robotide/editor/contentassist.py | 41 ++++++++++++++++++++-------- src/robotide/editor/kweditor.py | 5 +++- src/robotide/editor/macroeditors.py | 10 +++++++ src/robotide/editor/texteditor.py | 3 ++ src/robotide/namespace/namespace.py | 8 ++++++ 6 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/robotide/editor/__init__.py b/src/robotide/editor/__init__.py index 562af8e35..35982f076 100644 --- a/src/robotide/editor/__init__.py +++ b/src/robotide/editor/__init__.py @@ -307,6 +307,7 @@ def on_uncomment_cells(self, event): def on_content_assistance(self, event): __ = event + print(f"DEBUG: init.py _EditorTab on_content_assistance event={event}") self.editor.show_content_assist() def save(self, message=None): diff --git a/src/robotide/editor/contentassist.py b/src/robotide/editor/contentassist.py index d691501ac..b5ba1a067 100755 --- a/src/robotide/editor/contentassist.py +++ b/src/robotide/editor/contentassist.py @@ -97,6 +97,8 @@ def on_key_down(self, event): key_code, alt_down = event.GetKeyCode(), event.AltDown() control_down = event.CmdDown() or event.ControlDown() key_char = event.GetUnicodeKey() + # print(f"DEBUG: contentassist.pt on_key_down key_code={key_code} cha={key_char} controlDown={control_down} " + # f"\n L/R ARROW = {key_code in (wx.WXK_RIGHT, wx.WXK_LEFT)}") if key_char not in [ord('['), ord('{'), ord('('), ord("'"), ord('\"'), ord('`')]: self._selection = self.GetStringSelection() # Ctrl-Space handling needed for dialogs # DEBUG add Ctrl-m @@ -110,12 +112,18 @@ def on_key_down(self, event): self.SetInsertionPoint(len(value)) self._popup.hide() self.reset() + """ else: event.Skip() + """ + event.Skip() elif key_code == wx.WXK_RETURN and self._popup.is_shown(): self.on_focus_lost(event) elif key_code == wx.WXK_TAB: - self.on_focus_lost(event, False) + # self.on_focus_lost(event, True) + self.fill_suggestion() # accept value and continue editing + # event.Skip() + # wx.CallAfter(self._show_auto_suggestions_when_enabled) elif key_code == wx.WXK_ESCAPE and self._popup.is_shown(): self._popup.hide() elif key_code in [wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PAGEUP, wx.WXK_PAGEDOWN] and self._popup.is_shown(): @@ -150,7 +158,7 @@ def _show_auto_suggestions_when_enabled(self): def on_char(self, event): key_char = event.GetUnicodeKey() - if key_char != wx.WXK_RETURN: + if 32 <= key_char < 256: # != wx.WXK_RETURN: Was activating popup with left/right arrow self._show_auto_suggestions_when_enabled() if key_char == wx.WXK_NONE: event.Skip() @@ -260,6 +268,7 @@ def _remove_text(value, remove_text, on_the_left, on_the_right, from_, to_): return value def on_focus_lost(self, event, set_value=True): + print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase on_focus_lost ENTER {event}") event.Skip() if not self._popup.is_shown(): return @@ -278,14 +287,17 @@ def _get_popup_suggestion(self, in_value=None): popup_value = self._popup.get_value() else: popup_value = in_value - if popup_value and popup_value.lower() in initial_value.lower(): - initial_value = initial_value.replace(initial_value, '') - parts = initial_value.split() - for p in parts: - if popup_value and popup_value.lower().startswith(p.strip('}])').lower()): - idx = initial_value.index(p) - initial_value = initial_value[:idx] - break + if popup_value: + if popup_value.lower() in initial_value.lower(): + initial_value = initial_value.replace(initial_value, '') + parts = initial_value.split() + for p in parts: + if popup_value and popup_value.lower().startswith(p.strip('}])').lower()): + idx = initial_value.index(p) + initial_value = initial_value[:idx] + break + else: + popup_value = '' if self.gherkin_prefix: initial_value = initial_value.replace(self.gherkin_prefix, '') # Should be left replace value = self.gherkin_prefix + initial_value + popup_value # or self.GetValue() @@ -295,7 +307,7 @@ def _get_popup_suggestion(self, in_value=None): def fill_suggestion(self, value=None): value = self._get_popup_suggestion(value) - # print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase fill_suggestion writting value={value}") + print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase fill_suggestion writting value={value}") if value: wrapper_view = self.GetParent().GetParent() if hasattr(wrapper_view, 'open_cell_editor'): @@ -304,6 +316,7 @@ def fill_suggestion(self, value=None): self.SetValue(value) self.SetInsertionPoint(len(value)) self.hide() + # self.reset() def pop_event_handlers(self, event): __ = event @@ -325,6 +338,8 @@ def reset(self): self._showing_content_assist = False def show_content_assist(self): + print(f"DEBUG: contentassist.py show_content_assist ENTER " + f"self._showing_content_assist={self._showing_content_assist}") if self._showing_content_assist: return if self._populate_content_assist(): @@ -351,6 +366,8 @@ def _remove_bdd_prefix(self, name): def _show_content_assist(self): _, height = self.GetSize() x, y = self.ClientToScreen((0, 0)) + print(f"DEBUG: contentassist.py _show_content_assist COORDS {x=}, {y=}, {height=}" + f" CALL self._popup.show()") self._popup.show(x, y, height) def content_assist_value(self): @@ -566,7 +583,7 @@ def content_assist_for(self, value, row=None): return False self._choices = list(set([c for c in self._choices if c is not None])) # print(f"DEBUG: contentassist.py ContentAssistPopup content_assist_for CALL POPULATE Choices={self._choices}") - self._list.populate(self._choices) + self._list.populate(sorted(self._choices)) return True @staticmethod diff --git a/src/robotide/editor/kweditor.py b/src/robotide/editor/kweditor.py index 20f1cdff5..218350a28 100755 --- a/src/robotide/editor/kweditor.py +++ b/src/robotide/editor/kweditor.py @@ -759,6 +759,8 @@ def save(self): cell_editor.EndEdit(self.selection.topleft.row, self.selection.topleft.col, self) def show_content_assist(self): + print(f"DEBUG: kweditor.py KeyworkEditor calling show_content_assist PARENT SECTION {self._parent.section}" + f" IsCellEditControlShown={self.IsCellEditControlShown()}") if self.IsCellEditControlShown(): self.GetCellEditor(*self.selection.cell).show_content_assist(self.selection.cell) @@ -1297,6 +1299,7 @@ def __init__(self, plugin, controller, language='En'): def show_content_assist(self, args=None): _ = args + print(f"DEBUG: kweditor.py show_content_assist args={args}") if self._tc: self._tc.show_content_assist() @@ -1381,7 +1384,7 @@ def StartingKey(self, event): key = event.GetKeyCode() event.Skip() # DEBUG seen this skip as soon as possible if key == wx.WXK_DELETE or key > 255: - # print(f"DEBUG: Delete key at ContentAssist key {key}") + print(f"DEBUG: Delete key at ContentAssist key {key}") self._grid.HideCellEditControl() elif key == wx.WXK_BACK: self._tc.SetValue(self._original_value) diff --git a/src/robotide/editor/macroeditors.py b/src/robotide/editor/macroeditors.py index 66037924d..1daf74799 100644 --- a/src/robotide/editor/macroeditors.py +++ b/src/robotide/editor/macroeditors.py @@ -24,10 +24,15 @@ _ = wx.GetTranslation # To keep linter/code analyser happy builtins.__dict__['_'] = wx.GetTranslation + class TestCaseEditor(_RobotTableEditor): __test__ = False _settings_open_id = 'test case settings open' + def __init__(self, plugin, parent, _controller, tree): + self.section = 'tests' # To use with private keywords - should not use in tests o tasks + _RobotTableEditor.__init__(self, plugin, parent, _controller, tree) + def _populate(self): self.header = self._create_header(self.controller.name) self.sizer.Add(self.header, 0, wx.EXPAND | wx.ALL, 5) @@ -117,12 +122,17 @@ def uncomment_cells(self): self.kweditor.on_uncomment_cells(None) def show_content_assist(self): + print(f"DEBUG: macroeditors.py TestCaseEditor calling show_content_assist SECTION {self.section}") self.kweditor.show_content_assist() class UserKeywordEditor(TestCaseEditor): _settings_open_id = 'user keyword settings open' + def __init__(self, plugin, parent, _controller, tree): + self.section = 'keywords' # To use with private keywords - can use in keywords sections if defined in file + TestCaseEditor.__init__(self, plugin, parent, _controller, tree) + def _create_header(self, text, readonly=False): if readonly: text += _(' (READ ONLY)') diff --git a/src/robotide/editor/texteditor.py b/src/robotide/editor/texteditor.py index cef5493ba..a7f83a791 100644 --- a/src/robotide/editor/texteditor.py +++ b/src/robotide/editor/texteditor.py @@ -534,6 +534,9 @@ def on_data_changed(self, message): pass def _on_timer(self, event): + if not self.is_focused(): # Was reaching here with contentassist in kweditor + event.Skip() + return self._editor.store_position() self._open_tree_selection_in_editor() event.Skip() diff --git a/src/robotide/namespace/namespace.py b/src/robotide/namespace/namespace.py index 25852f466..90205f181 100644 --- a/src/robotide/namespace/namespace.py +++ b/src/robotide/namespace/namespace.py @@ -233,8 +233,12 @@ def new_resource(self, path, directory=''): def find_user_keyword(self, datafile, kw_name): kw = self.find_keyword(datafile, kw_name) + # if kw: + # print(f"DEBUG: namespace.py Namespace find_user_keyword kw_name=={kw_name}" + # f" datafile={datafile.source} kw.source={kw.source}") return kw if isinstance(kw, UserKeywordInfo) else None + # DEBUG: This keyword is not used, Delete or use kw.is_private_keyword from ItemInfo def is_private(self, datafile, kwname): # TODO: Add condition for robot:private in tags return kwname.startswith('_') and self.is_user_keyword(datafile, kwname) @@ -690,6 +694,8 @@ def get(self, kw_name, origin=None): # filename = os.path.basename(origin.source) # print(f"DEBUG: namespace.py _Keywords get keywords in loop FOUND {kw_name} @ {filename}" # f" RETURNING {self.keywords[kw_name]} {self.keywords[kw_name].source == filename}") + # print(f"DEBUG: namespace.py _Keywords get keywords in loop FOUND {kw_name}" + # f" source={self.keywords[kw_name].source}") return self.keywords[kw_name] # print(f"DEBUG: namespace.py _Keywords get keywords {self.keywords}") bdd_name = self._get_bdd_name(kw_name) @@ -700,6 +706,8 @@ def get(self, kw_name, origin=None): for regexp in self.embedded_keywords: try: if regexp.match(kw_name) or (bdd_name and regexp.match(bdd_name)): + # print(f"DEBUG: namespace.py _Keywords get keywords in REGEX FOUND {kw_name}" + # f" source={self.embedded_keywords[regexp].source}") return self.embedded_keywords[regexp] except AttributeError: pass From 484ed8afd67bacdd2ca146d7393465383bf76998 Mon Sep 17 00:00:00 2001 From: HelioGuilherme66 Date: Sat, 5 Jul 2025 03:35:05 +0100 Subject: [PATCH 06/17] ENTER fills cell with selection. Tab to select and keep edit working, WIP --- src/robotide/editor/contentassist.py | 44 +++++++++++++++++++++------- src/robotide/editor/kweditor.py | 1 + src/robotide/version.py | 2 +- 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/robotide/editor/contentassist.py b/src/robotide/editor/contentassist.py index b5ba1a067..f48cf12ab 100755 --- a/src/robotide/editor/contentassist.py +++ b/src/robotide/editor/contentassist.py @@ -56,6 +56,8 @@ def __init__(self, suggestion_source, language='En', **kw): self.color_foreground_text = self.general_settings['foreground text'] self.language = language self._popup = ContentAssistPopup(self, suggestion_source) + # print(f"DEBUG: contentassist.py _ContentAssistTextCtrlBase INIT after _popup={self._popup}" + # f" suggestion_source = {suggestion_source}") self.Bind(wx.EVT_KEY_DOWN, self.on_key_down) self.Bind(wx.EVT_CHAR, self.on_char) self.Bind(wx.EVT_KILL_FOCUS, self.on_focus_lost) @@ -118,10 +120,14 @@ def on_key_down(self, event): """ event.Skip() elif key_code == wx.WXK_RETURN and self._popup.is_shown(): + print(f"DEBUG: contentassist.pt on_key_down POP SHOWN PRESS RETURN CALL FOCUS LOST") self.on_focus_lost(event) elif key_code == wx.WXK_TAB: # self.on_focus_lost(event, True) + print(f"DEBUG: contentassist.pt on_key_down PRESS TAB") self.fill_suggestion() # accept value and continue editing + self._popup.hide() + self.reset() # event.Skip() # wx.CallAfter(self._show_auto_suggestions_when_enabled) elif key_code == wx.WXK_ESCAPE and self._popup.is_shown(): @@ -141,6 +147,7 @@ def on_key_down(self, event): wx.CallAfter(self._show_auto_suggestions_when_enabled) # Can not catch the following keyEvent from grid cell elif key_code == wx.WXK_RETURN: + print(f"DEBUG: contentassist.pt on_key_down PRESS RETURN FILL AND SKIP") # fill suggestion in dialogs when pressing enter self.fill_suggestion() event.Skip() @@ -268,7 +275,7 @@ def _remove_text(value, remove_text, on_the_left, on_the_right, from_, to_): return value def on_focus_lost(self, event, set_value=True): - print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase on_focus_lost ENTER {event}") + # print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase on_focus_lost ENTER {event}") event.Skip() if not self._popup.is_shown(): return @@ -291,11 +298,16 @@ def _get_popup_suggestion(self, in_value=None): if popup_value.lower() in initial_value.lower(): initial_value = initial_value.replace(initial_value, '') parts = initial_value.split() - for p in parts: - if popup_value and popup_value.lower().startswith(p.strip('}])').lower()): - idx = initial_value.index(p) - initial_value = initial_value[:idx] - break + # print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase PARTS={parts[:]}\n ") + if parts: + for p in parts[::-1]: + clean = p.strip('$@&%{[(}])\'\"').lower() + # print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase clean={clean}") + if popup_value.strip('$@&%{[(}])\'\"').lower().startswith(clean): + idx = initial_value.index(p) + initial_value = initial_value[:idx] + # print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase INITIAL VALUE ={initial_value}") + break else: popup_value = '' if self.gherkin_prefix: @@ -303,8 +315,10 @@ def _get_popup_suggestion(self, in_value=None): value = self.gherkin_prefix + initial_value + popup_value # or self.GetValue() else: value = initial_value + popup_value # or self.GetValue() + # print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase _get_popup_suggestion RETURN value={value}") return value + # DEBUG THIS IS NOT BEING CALLED def fill_suggestion(self, value=None): value = self._get_popup_suggestion(value) print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase fill_suggestion writting value={value}") @@ -315,7 +329,7 @@ def fill_suggestion(self, value=None): wrapper_view.open_cell_editor() self.SetValue(value) self.SetInsertionPoint(len(value)) - self.hide() + # self.hide() # self.reset() def pop_event_handlers(self, event): @@ -338,8 +352,8 @@ def reset(self): self._showing_content_assist = False def show_content_assist(self): - print(f"DEBUG: contentassist.py show_content_assist ENTER " - f"self._showing_content_assist={self._showing_content_assist}") + # print(f"DEBUG: contentassist.py show_content_assist ENTER " + # f"self._showing_content_assist={self._showing_content_assist}") if self._showing_content_assist: return if self._populate_content_assist(): @@ -366,12 +380,14 @@ def _remove_bdd_prefix(self, name): def _show_content_assist(self): _, height = self.GetSize() x, y = self.ClientToScreen((0, 0)) - print(f"DEBUG: contentassist.py _show_content_assist COORDS {x=}, {y=}, {height=}" - f" CALL self._popup.show()") + # print(f"DEBUG: contentassist.py _show_content_assist COORDS {x=}, {y=}, {height=}" + # f" CALL self._popup.show()") self._popup.show(x, y, height) def content_assist_value(self): suggestion = self._popup.content_assist_value(self.Value) + # print(f"DEBUG: contentassist.py _ContentAssistTextCtrlBase content_assist_value " + # f" self.gherkin_prefix={self.gherkin_prefix} suggestion={suggestion}") if suggestion is None: return suggestion else: @@ -566,6 +582,7 @@ def __init__(self, parent, suggestion_source): self.on_list_item_activated) self._suggestions = Suggestions(suggestion_source) self._choices = None + # print(f"DEBUG: contentassist.py ContentAssistPopup INIT suggestion_source={suggestion_source}") def reset(self): self._selection = -1 @@ -576,6 +593,9 @@ def get_value(self): def content_assist_for(self, value, row=None): self._choices = self._suggestions.get_for(value, row=row) + if not self._choices and ' ' in value: # Find choices for last word + self._choices = self._suggestions.get_for(value.split()[-1], row=row) + # print(f"DEBUG: contentassist.py ContentAssistPopup content_assist_for value={value} choices={self._choices}") if not self._choices: self._list.ClearAll() if not isinstance(self._parent, GridEditor): @@ -593,6 +613,8 @@ def _starts(val1, val2): def content_assist_value(self, value): _ = value # DEBUG: why we have this argument if self._selection > -1: + # print(f"DEBUG: contentassist.py ContentAssistPopup content_assist_value RETURN text of " + # f"selection idx={self._selection} item={self._list.GetItem(self._selection)}") return self._list.GetItem(self._selection).GetText() return None diff --git a/src/robotide/editor/kweditor.py b/src/robotide/editor/kweditor.py index 218350a28..fb4440668 100755 --- a/src/robotide/editor/kweditor.py +++ b/src/robotide/editor/kweditor.py @@ -1374,6 +1374,7 @@ def ApplyEdit(self, row, col, gridd): def _get_value(self): suggestion = self._tc.content_assist_value() + # print(f"DEBUG: kweditor.py ContentAssistCellEditor suggestion={suggestion}") return suggestion or self._tc.GetValue() def Reset(self): diff --git a/src/robotide/version.py b/src/robotide/version.py index 00ae1cf5b..d0f1114b4 100644 --- a/src/robotide/version.py +++ b/src/robotide/version.py @@ -15,4 +15,4 @@ # # Automatically generated by `tasks.py`. -VERSION = 'v2.2dev32' +VERSION = 'v2.2dev33' From 953955fc5ffdb304a45bd1ffef9d30c6a8d3ed94 Mon Sep 17 00:00:00 2001 From: HelioGuilherme66 Date: Sun, 6 Jul 2025 05:25:03 +0100 Subject: [PATCH 07/17] Fixed list selection in Cell Editor. Values in Grid not saved to model WIP --- src/robotide/editor/contentassist.py | 18 +++++++----- src/robotide/editor/kweditor.py | 44 +++++++++++++++++++++------- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/robotide/editor/contentassist.py b/src/robotide/editor/contentassist.py index f48cf12ab..b714d5fe6 100755 --- a/src/robotide/editor/contentassist.py +++ b/src/robotide/editor/contentassist.py @@ -120,11 +120,11 @@ def on_key_down(self, event): """ event.Skip() elif key_code == wx.WXK_RETURN and self._popup.is_shown(): - print(f"DEBUG: contentassist.pt on_key_down POP SHOWN PRESS RETURN CALL FOCUS LOST") + # print(f"DEBUG: contentassist.pt on_key_down POP SHOWN PRESS RETURN CALL FOCUS LOST") self.on_focus_lost(event) elif key_code == wx.WXK_TAB: # self.on_focus_lost(event, True) - print(f"DEBUG: contentassist.pt on_key_down PRESS TAB") + # print(f"DEBUG: contentassist.pt on_key_down PRESS TAB") self.fill_suggestion() # accept value and continue editing self._popup.hide() self.reset() @@ -147,7 +147,7 @@ def on_key_down(self, event): wx.CallAfter(self._show_auto_suggestions_when_enabled) # Can not catch the following keyEvent from grid cell elif key_code == wx.WXK_RETURN: - print(f"DEBUG: contentassist.pt on_key_down PRESS RETURN FILL AND SKIP") + # print(f"DEBUG: contentassist.pt on_key_down PRESS RETURN FILL AND SKIP") # fill suggestion in dialogs when pressing enter self.fill_suggestion() event.Skip() @@ -318,10 +318,10 @@ def _get_popup_suggestion(self, in_value=None): # print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase _get_popup_suggestion RETURN value={value}") return value - # DEBUG THIS IS NOT BEING CALLED + # DEBUG THIS IS BEING CALLED FROM kweditor def fill_suggestion(self, value=None): value = self._get_popup_suggestion(value) - print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase fill_suggestion writting value={value}") + # print(f"DEBUG: contentassist.py ContentAssistTextCtrlBase fill_suggestion writting value={value}") if value: wrapper_view = self.GetParent().GetParent() if hasattr(wrapper_view, 'open_cell_editor'): @@ -329,9 +329,12 @@ def fill_suggestion(self, value=None): wrapper_view.open_cell_editor() self.SetValue(value) self.SetInsertionPoint(len(value)) - # self.hide() + self.hide() # self.reset() + def get_value(self): + return self.GetValue() + def pop_event_handlers(self, event): __ = event # all pushed eventHandlers need to be popped before close @@ -717,7 +720,8 @@ def populate(self, data): self.InsertColumn(0, '', width=self.Size[0]) for row, item in enumerate(data): self.InsertItem(row, item) - self.Select(0) + if self.GetItemCount() > 0: + self.Select(0) def get_text(self, index): return self.GetItem(index).GetText() diff --git a/src/robotide/editor/kweditor.py b/src/robotide/editor/kweditor.py index fb4440668..461e768aa 100755 --- a/src/robotide/editor/kweditor.py +++ b/src/robotide/editor/kweditor.py @@ -757,10 +757,14 @@ def save(self): if self.IsCellEditControlShown(): cell_editor = self.GetCellEditor(*self.selection.cell) cell_editor.EndEdit(self.selection.topleft.row, self.selection.topleft.col, self) + value = cell_editor.get_value() + if value: + wx.CallAfter(self.cell_value_edited, self.selection.topleft.row, + self.selection.topleft.col, value) def show_content_assist(self): - print(f"DEBUG: kweditor.py KeyworkEditor calling show_content_assist PARENT SECTION {self._parent.section}" - f" IsCellEditControlShown={self.IsCellEditControlShown()}") + # print(f"DEBUG: kweditor.py KeyworkEditor calling show_content_assist PARENT SECTION {self._parent.section}" + # f" IsCellEditControlShown={self.IsCellEditControlShown()}") if self.IsCellEditControlShown(): self.GetCellEditor(*self.selection.cell).show_content_assist(self.selection.cell) @@ -842,7 +846,16 @@ def _call_ctrl_function(self, event: object, keycode: int): return True def _call_direct_function(self, event: wx.KeyEvent, keycode: int): - if keycode == wx.WXK_WINDOWS_MENU: + if keycode == wx.WXK_TAB: + if self.IsCellEditControlShown(): + # print(f"DEBUG: kweditor.py KeywordEditor _call_direct_function PRESSED TAB key={keycode}") + self._get_cell_editor().update_from_suggestion_list() # accept value and continue editing + self.save() + else: + self.save() + self._move_grid_cursor(event, keycode) + return False + elif keycode == wx.WXK_WINDOWS_MENU: self.on_cell_right_click(event) elif keycode == wx.WXK_BACK: self._move_grid_cursor(event, keycode) @@ -850,6 +863,7 @@ def _call_direct_function(self, event: wx.KeyEvent, keycode: int): if self.IsCellEditControlShown(): # fill auto-suggestion into cell when pressing enter self._get_cell_editor().update_from_suggestion_list() + self.save() self._move_grid_cursor(event, keycode) else: self.open_cell_editor() @@ -978,7 +992,12 @@ def _move_rows(self, keycode): def _move_grid_cursor(self, event, keycode): self.DisableCellEditControl() - if keycode == wx.WXK_RETURN: + if keycode == wx.WXK_TAB: + if event.ShiftDown(): + self.MoveCursorLeft(False) + else: + self.MoveCursorRight(False) + elif keycode == wx.WXK_RETURN: self.MoveCursorRight(event.ShiftDown()) else: self.MoveCursorLeft(event.ShiftDown()) @@ -1278,6 +1297,7 @@ def var_strip(txt:str): return sorted(words) """ + class ContentAssistCellEditor(GridCellEditor): def __init__(self, plugin, controller, language='En'): @@ -1297,9 +1317,9 @@ def __init__(self, plugin, controller, language='En'): self._counter = 0 self._height = 0 - def show_content_assist(self, args=None): + def show_content_assist(self, args=None): # TODO: check if this is ever called _ = args - print(f"DEBUG: kweditor.py show_content_assist args={args}") + # print(f"DEBUG: kweditor.py show_content_assist args={args}") if self._tc: self._tc.show_content_assist() @@ -1372,20 +1392,24 @@ def ApplyEdit(self, row, col, gridd): # this will cause deleting all text in edit mode not working self._grid.cell_value_edited(row, col, self._value) - def _get_value(self): + def _get_value(self): # TODO: Check why most of the times is getting None suggestion = self._tc.content_assist_value() - # print(f"DEBUG: kweditor.py ContentAssistCellEditor suggestion={suggestion}") + # print(f"DEBUG: kweditor.py ContentAssistCellEditor _get_value suggestion={suggestion}") return suggestion or self._tc.GetValue() + def get_value(self): + return self._tc.get_value() + def Reset(self): self._tc.SetValue(self._original_value) self._tc.reset() - def StartingKey(self, event): + def StartingKey(self, event): # TODO: Check why this is never called key = event.GetKeyCode() + # print(f"DEBUG: kweditor.py ContentAssistCellEditor StartingKey pressed key={key}") event.Skip() # DEBUG seen this skip as soon as possible if key == wx.WXK_DELETE or key > 255: - print(f"DEBUG: Delete key at ContentAssist key {key}") + # print(f"DEBUG: Delete key at ContentAssist key {key}") self._grid.HideCellEditControl() elif key == wx.WXK_BACK: self._tc.SetValue(self._original_value) From 27811357b390be4ca3ba88afebdc3d9fbd0f25d9 Mon Sep 17 00:00:00 2001 From: HelioGuilherme66 Date: Sun, 6 Jul 2025 13:48:46 +0100 Subject: [PATCH 08/17] Update documentation --- CHANGELOG.adoc | 11 ++++++++++- src/robotide/application/releasenotes.py | 7 ++++++- src/robotide/editor/contentassist.py | 9 ++++----- src/robotide/editor/kweditor.py | 1 + 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index a85e5a0d3..c6c9b7564 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -6,7 +6,16 @@ All notable changes to this project will be documented in this file. The format is based on http://keepachangelog.com/en/1.0.0/[Keep a Changelog] and this project adheres to http://semver.org/spec/v2.0.0.html[Semantic Versioning]. -// == https://github.com/robotframework/RIDE[Unreleased] +== https://github.com/robotframework/RIDE[Unreleased] + +=== Added +- Added indication of *private* keywords in Details pop-up for keywords with tag *robot:private* or name starting with underscore, *'_'*. + +=== Changed +- Modified the action of key TAB when selecting from auto-suggestions list in Grid Editor. Pressing TAB, selects the item and continues in cell editor. + +=== Fixed +- Fix cursor position when editing cells in Grid Editor. == https://github.com/robotframework/RIDE/blob/master/doc/releasenotes/ride-2.1.4.1.rst[2.1.4.1] - 2025-06-24 diff --git a/src/robotide/application/releasenotes.py b/src/robotide/application/releasenotes.py index 1e7e49398..f7de34002 100644 --- a/src/robotide/application/releasenotes.py +++ b/src/robotide/application/releasenotes.py @@ -172,6 +172,11 @@ def set_content(self, html_win, content):

New Features and Fixes Highlights

    +
  • Added indication of private keywords in Details pop-up for keywords with tag robot:private or name starting + with underscore, '_'
  • +
  • Modified the action of key TAB when selecting from auto-suggestions list in Grid Editor. Pressing TAB, selects the + item and continues in cell editor.
  • +
  • Fix cursor position when editing cells in Grid Editor.
  • Fix broken installation of RIDE v2.1.4 by adding missing dependencies.
  • Added Tools->Library Finder... to install libraries and Help->Open Library Documentation... . They share the same dialog, and definitions are recorded in ``settings.cfg``.
  • @@ -248,7 +253,7 @@ def set_content(self, html_win, content):
    python -m robotide.postinstall -install

    or

    ride_postinstall.py -install
    -

    RIDE {VERSION} was released on 24/June/2025.

    +

    RIDE {VERSION} was released on 06/July/2025.