From 9987a1a21e08e35d238c918441d92420583cdf9d Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 8 Mar 2026 14:57:19 -0500 Subject: [PATCH] refactor(code): move temp cut buffer feature and clean up related code - Moved cut_to_temp_buffer and paste_temp_buffer commands to plugin - Remove temporary cut buffer logic from SourceView - Remove related keybindings (Ctrl+K / Ctrl+U) - Fix word-end detection in ProviderResponseCacheBase to avoid extending past line end - Improve completion controller to support RequestCompletionEvent with optional provider filtering - Add GetActiveViewEvent handling in SourceViewsController - Ensure view scrolls to cursor after buffer switch via GLib.idle_add - Emit focus-in-event after DnD file load to restore focus behavior - Prevent "buffer" pseudo-path from interfering with loaded file filtering - Remove obsolete AGENTS.md from LSP manager plugin --- .../commands/nanoesq_temp_buffer/__init__.py | 3 + .../commands/nanoesq_temp_buffer/__main__.py | 3 + .../nanoesq_temp_buffer/cut_to_temp_buffer.py | 44 +++++ .../commands/nanoesq_temp_buffer/helpers.py | 24 +++ .../nanoesq_temp_buffer/manifest.json | 7 + .../nanoesq_temp_buffer/paste_temp_buffer.py | 35 ++++ .../commands/nanoesq_temp_buffer/plugin.py | 43 +++++ plugins/code/ui/lsp_manager/AGENTS.md | 159 ------------------ .../commands/cut_to_temp_buffer.py | 38 ----- .../commands/dnd_load_file_to_buffer.py | 7 +- .../commands/paste_temp_buffer.py | 30 ---- .../provider_response_cache_base.py | 4 +- .../code/controllers/completion_controller.py | 17 +- .../code/controllers/files_controller.py | 3 +- .../code/controllers/views/signal_mapper.py | 10 ++ .../views/source_views_controller.py | 4 +- src/core/widgets/code/source_view.py | 16 -- .../usr/share/app_name/code-key-bindings.json | 6 - 18 files changed, 194 insertions(+), 259 deletions(-) create mode 100644 plugins/code/commands/nanoesq_temp_buffer/__init__.py create mode 100644 plugins/code/commands/nanoesq_temp_buffer/__main__.py create mode 100644 plugins/code/commands/nanoesq_temp_buffer/cut_to_temp_buffer.py create mode 100644 plugins/code/commands/nanoesq_temp_buffer/helpers.py create mode 100644 plugins/code/commands/nanoesq_temp_buffer/manifest.json create mode 100644 plugins/code/commands/nanoesq_temp_buffer/paste_temp_buffer.py create mode 100644 plugins/code/commands/nanoesq_temp_buffer/plugin.py delete mode 100644 plugins/code/ui/lsp_manager/AGENTS.md delete mode 100644 src/core/widgets/code/command_system/commands/cut_to_temp_buffer.py delete mode 100644 src/core/widgets/code/command_system/commands/paste_temp_buffer.py diff --git a/plugins/code/commands/nanoesq_temp_buffer/__init__.py b/plugins/code/commands/nanoesq_temp_buffer/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/code/commands/nanoesq_temp_buffer/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/code/commands/nanoesq_temp_buffer/__main__.py b/plugins/code/commands/nanoesq_temp_buffer/__main__.py new file mode 100644 index 0000000..a576329 --- /dev/null +++ b/plugins/code/commands/nanoesq_temp_buffer/__main__.py @@ -0,0 +1,3 @@ +""" + Pligin Package +""" diff --git a/plugins/code/commands/nanoesq_temp_buffer/cut_to_temp_buffer.py b/plugins/code/commands/nanoesq_temp_buffer/cut_to_temp_buffer.py new file mode 100644 index 0000000..15cf79e --- /dev/null +++ b/plugins/code/commands/nanoesq_temp_buffer/cut_to_temp_buffer.py @@ -0,0 +1,44 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource + +# Application imports +from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed + + + +class Handler: + @staticmethod + def execute( + view: GtkSource.View, + *args, + **kwargs + ): + logger.debug("Command: Cut to Temp Buffer") + + clear_temp_cut_buffer_delayed(view) + + buffer = view.get_buffer() + itr = buffer.get_iter_at_mark(buffer.get_insert()) + + start_itr = itr.copy() + start_itr.set_line_offset(0) + + end_itr = start_itr.copy() + if not end_itr.forward_line(): + end_itr = buffer.get_end_iter() + + if not hasattr(view, "_cut_buffer"): + view._cut_buffer = "" + + line_str = buffer.get_text(start_itr, end_itr, True) + view._cut_buffer += line_str + + buffer.delete(start_itr, end_itr) + + set_temp_cut_buffer_delayed(view) diff --git a/plugins/code/commands/nanoesq_temp_buffer/helpers.py b/plugins/code/commands/nanoesq_temp_buffer/helpers.py new file mode 100644 index 0000000..86a8671 --- /dev/null +++ b/plugins/code/commands/nanoesq_temp_buffer/helpers.py @@ -0,0 +1,24 @@ +# Python imports + +# Lib imports +import gi + +from gi.repository import GLib + +# Application imports + + + +def clear_temp_cut_buffer_delayed(view: any): + if not hasattr(view, "_cut_temp_timeout_id"): return + if not view._cut_temp_timeout_id: return + + GLib.source_remove(view._cut_temp_timeout_id) + +def set_temp_cut_buffer_delayed(view: any): + def clear_temp_buffer(view: any): + view._cut_buffer = "" + view._cut_temp_timeout_id = None + return False + + view._cut_temp_timeout_id = GLib.timeout_add(15000, clear_temp_buffer, view) diff --git a/plugins/code/commands/nanoesq_temp_buffer/manifest.json b/plugins/code/commands/nanoesq_temp_buffer/manifest.json new file mode 100644 index 0000000..383903a --- /dev/null +++ b/plugins/code/commands/nanoesq_temp_buffer/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Nanoesq Temp Buffer", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": {} +} diff --git a/plugins/code/commands/nanoesq_temp_buffer/paste_temp_buffer.py b/plugins/code/commands/nanoesq_temp_buffer/paste_temp_buffer.py new file mode 100644 index 0000000..be8d7e2 --- /dev/null +++ b/plugins/code/commands/nanoesq_temp_buffer/paste_temp_buffer.py @@ -0,0 +1,35 @@ +# Python imports + +# Lib imports +import gi + +gi.require_version('GtkSource', '4') + +from gi.repository import GLib +from gi.repository import GtkSource + +# Application imports +from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed + + +class Handler2: + @staticmethod + def execute( + view: GtkSource.View, + *args, + **kwargs + ): + logger.debug("Command: Paste Temp Buffer") + if not hasattr(view, "_cut_temp_timeout_id"): return + if not hasattr(view, "_cut_buffer"): return + if not view._cut_buffer: return + + clear_temp_cut_buffer_delayed(view) + + buffer = view.get_buffer() + itr = buffer.get_iter_at_mark( buffer.get_insert() ) + insert_itr = itr.copy() + + buffer.insert(insert_itr, view._cut_buffer, -1) + + set_temp_cut_buffer_delayed(view) diff --git a/plugins/code/commands/nanoesq_temp_buffer/plugin.py b/plugins/code/commands/nanoesq_temp_buffer/plugin.py new file mode 100644 index 0000000..392caf0 --- /dev/null +++ b/plugins/code/commands/nanoesq_temp_buffer/plugin.py @@ -0,0 +1,43 @@ +# Python imports + +# Lib imports + +# Application imports +from libs.event_factory import Event_Factory, Code_Event_Types + +from plugins.plugin_types import PluginCode + +from .cut_to_temp_buffer import Handler +from .paste_temp_buffer import Handler2 + + + +class Plugin(PluginCode): + def __init__(self): + super(Plugin, self).__init__() + + + def _controller_message(self, event: Code_Event_Types.CodeEvent): + ... + + def load(self): + event = Event_Factory.create_event("register_command", + command_name = "cut_to_temp_buffer", + command = Handler, + binding_mode = "held", + binding = "k" + ) + + self.emit_to("source_views", event) + + event = Event_Factory.create_event("register_command", + command_name = "paste_temp_buffer", + command = Handler2, + binding_mode = "held", + binding = "u" + ) + + self.emit_to("source_views", event) + + def run(self): + ... diff --git a/plugins/code/ui/lsp_manager/AGENTS.md b/plugins/code/ui/lsp_manager/AGENTS.md deleted file mode 100644 index 7e5ab76..0000000 --- a/plugins/code/ui/lsp_manager/AGENTS.md +++ /dev/null @@ -1,159 +0,0 @@ -# AGENTS.md - LSP Manager Plugin - -## Project Overview - -This is a Newton editor plugin providing LSP (Language Server Protocol) code completion via WebSocket. Written in Python using GTK3/GtkSource. - -## Build/Lint/Test Commands - -### Running Tests - -```bash -# Run all websocket library tests -python -m unittest discover -s libs/websocket/tests - -# Run a single test file -python -m unittest libs.websocket.tests.test_websocket - -# Run a single test -python -m unittest libs.websocket.tests.test_websocket.WebSocketTest.test_default_timeout -``` - -### Environment - -- Python 3.x -- GTK 3.0 with GtkSource 4 -- No build system (pyproject.toml/setup.py) - direct execution - -## Code Style Guidelines - -### Import Organization - -Always use three-section ordering: -```python -# Python imports -import json -from os import path - -# Lib imports -import gi -gi.require_version('Gtk', '3.0') -gi.require_version('GtkSource', '4') -from gi.repository import Gtk - -# Application imports -from libs.event_factory import Event_Factory -from plugins.plugin_types import PluginCode -from .lsp_manager import LSPManager -``` - -### Formatting - -- 4-space indentation -- Line length: ~100 chars (soft limit) -- No trailing whitespace -- Use f-strings for string formatting: `f"{variable} text"` - -### Naming Conventions - -- **Classes**: PascalCase (`LSPManager`, `ProviderResponseCache`) -- **Methods/functions**: snake_case (`create_client`, `load_lsp_servers_config`) -- **Private members**: leading underscore (`_setup_styling`, `_init_params`) -- **Constants**: SCREAMING_SNAKE_CASE - -### Type Hints - -- Use Python 3.x type hints -- Common types: `str`, `int`, `dict`, `list`, `bool` -- For untyped parameters, use `any`: - ```python - def execute(view: any, *args, **kwargs) - ``` - -### Error Handling - -- Use try/except blocks with specific exception types -- Use `logger.error()` for logging errors with context -- Return early on failure conditions: - ```python - if not lang_id: return - if not lang_id in self.servers_config: return - ``` - -### Class Structure - -```python -class LSPManager(Gtk.Dialog): - def __init__(self): - super(LSPManager, self).__init__() - self._setup_styling() - self._setup_signals() - self._subscribe_to_events() - self._load_widgets() - - def _setup_styling(self): - ... - - def _setup_signals(self): - ... -``` - -### GTK Patterns - -- Use `gi.require_version()` before importing GTK modules -- Use `GLib.idle_add()` for deferred UI updates -- Connect signals with `widget.connect("signal_name", handler)` - -### Empty Methods - -Use ellipsis for stub/placeholder methods: -```python -def _subscribe_to_events(self): - ... -``` - -### Logging - -- Import `logger` from application (available globally) -- Use `logger.debug()`, `logger.error()` for appropriate levels -- Include context in error messages: - ```python - logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" ) - ``` - -### Inheritance Patterns - -```python -class Provider(GObject.GObject, GtkSource.CompletionProvider): - __gtype_name__ = 'LSPProvider' - - def do_get_name(self): - return "LSP Code Completion" -``` - -### Testing Patterns (websocket library) - -- Use `unittest.TestCase` -- Skip tests with decorators: - ```python - @unittest.skipUnless(TEST_WITH_INTERNET, "Internet-requiring tests are disabled") - def test_remote(self): - ... - ``` -- Environment variables for test configuration: - - `TEST_WITH_INTERNET=1` - enable internet tests - - `LOCAL_WS_SERVER_PORT=9999` - enable local server tests - -### File Organization - -- Main plugin: `plugin.py` -- Core logic: `lsp_manager.py`, `provider.py`, `provider_response_cache.py` -- Controllers: `controllers/` directory -- Config: `configs/` directory -- Embedded libs: `libs/websocket/` - -### Anti-Patterns to Avoid - -- Avoid bare `except:` - use specific exceptions -- Avoid global mutable state where possible -- Don't commit secrets or credentials diff --git a/src/core/widgets/code/command_system/commands/cut_to_temp_buffer.py b/src/core/widgets/code/command_system/commands/cut_to_temp_buffer.py deleted file mode 100644 index cae4e63..0000000 --- a/src/core/widgets/code/command_system/commands/cut_to_temp_buffer.py +++ /dev/null @@ -1,38 +0,0 @@ -# Python imports - -# Lib imports -import gi - -gi.require_version('GtkSource', '4') - -from gi.repository import GtkSource - -# Application imports - - - -def execute( - view: GtkSource.View, - *args, - **kwargs -): - logger.debug("Command: Cut to Temp Buffer") - - view.clear_temp_cut_buffer_delayed() - - buffer = view.get_buffer() - itr = buffer.get_iter_at_mark( buffer.get_insert() ) - start_itr = itr.copy() - end_itr = itr.copy() - start_line = itr.get_line() + 1 - start_char = itr.get_line_offset() - - start_itr.backward_visible_line() - start_itr.forward_line() - end_itr.forward_line() - - line_str = buffer.get_slice(start_itr, end_itr, True) - view._cut_buffer += f"{line_str}" - buffer.delete(start_itr, end_itr) - - view.set_temp_cut_buffer_delayed() diff --git a/src/core/widgets/code/command_system/commands/dnd_load_file_to_buffer.py b/src/core/widgets/code/command_system/commands/dnd_load_file_to_buffer.py index ff74819..165ad5f 100644 --- a/src/core/widgets/code/command_system/commands/dnd_load_file_to_buffer.py +++ b/src/core/widgets/code/command_system/commands/dnd_load_file_to_buffer.py @@ -6,6 +6,8 @@ import gi gi.require_version('GtkSource', '4') from gi.repository import GtkSource +from gi.repository import Gtk +from gi.repository import Gdk from gi.repository import Gio # Application imports @@ -20,9 +22,9 @@ def execute( **kwargs ): logger.debug("Command: DnD Load File To Buffer") - file = view.command.new_file(view) + file = view.command.new_file(view) + gfile = Gio.File.new_for_uri(uri) - gfile = Gio.File.new_for_uri(uri) view.command.exec_with_args( "load_file", view, gfile, file @@ -31,3 +33,4 @@ def execute( view.set_buffer(file.buffer) update_info_bar_if_focused(view.command, view) + view.emit("focus-in-event", Gdk.Event()) diff --git a/src/core/widgets/code/command_system/commands/paste_temp_buffer.py b/src/core/widgets/code/command_system/commands/paste_temp_buffer.py deleted file mode 100644 index 8c7cd7a..0000000 --- a/src/core/widgets/code/command_system/commands/paste_temp_buffer.py +++ /dev/null @@ -1,30 +0,0 @@ -# Python imports - -# Lib imports -import gi - -gi.require_version('GtkSource', '4') - -from gi.repository import GLib -from gi.repository import GtkSource - -# Application imports - - - -def execute( - view: GtkSource.View, - *args, - **kwargs -): - logger.debug("Command: Paste Temp Buffer") - - view.clear_temp_cut_buffer_delayed() - - buffer = view.get_buffer() - itr = buffer.get_iter_at_mark( buffer.get_insert() ) - insert_itr = itr.copy() - - buffer.insert(insert_itr, view._cut_buffer, -1) - - view.set_temp_cut_buffer_delayed() diff --git a/src/core/widgets/code/completion_providers/provider_response_cache_base.py b/src/core/widgets/code/completion_providers/provider_response_cache_base.py index 1c17f64..fe9e10e 100644 --- a/src/core/widgets/code/completion_providers/provider_response_cache_base.py +++ b/src/core/widgets/code/completion_providers/provider_response_cache_base.py @@ -109,8 +109,8 @@ class ProviderResponseCacheBase: if not start_iter.starts_word(): start_iter.backward_word_start() - if not end_iter.ends_word(): - end_iter.forward_word_end() + if not end_iter.ends_line() and not end_iter.ends_word(): + end_iter.forward_word_end() buffer = start_iter.get_buffer() diff --git a/src/core/widgets/code/controllers/completion_controller.py b/src/core/widgets/code/controllers/completion_controller.py index 1f9db98..a8fd9ed 100644 --- a/src/core/widgets/code/controllers/completion_controller.py +++ b/src/core/widgets/code/controllers/completion_controller.py @@ -43,8 +43,8 @@ class CompletionController(ControllerBase): self.provider_process_file_save(event) elif isinstance(event, Code_Event_Types.TextChangedEvent): self.provider_process_file_change(event) - # elif isinstance(event, Code_Event_Types.RequestCompletionEvent): - # self.request_unbound_completion( event.view.get_completion() ) + elif isinstance(event, Code_Event_Types.RequestCompletionEvent): + self.request_unbound_completion(event) def register_completer(self, completer: GtkSource.Completion): @@ -92,8 +92,17 @@ class CompletionController(ControllerBase): for provider in self._providers.values(): provider.response_cache.process_file_change(event) - def request_unbound_completion(self, completer: GtkSource.Completion): + def request_unbound_completion(self, event: Code_Event_Types.RequestCompletionEvent): + completer = event.view.get_completion() + providers = [ *self._providers.values() ] + + if event.provider: + if not isinstance(event.provider, list): + providers = [ event.provider ] + else: + providers = event.provider + completer.start( - [ *self._providers.values() ], + providers, completer.create_context() ) diff --git a/src/core/widgets/code/controllers/files_controller.py b/src/core/widgets/code/controllers/files_controller.py index ba660b2..6f7fa00 100644 --- a/src/core/widgets/code/controllers/files_controller.py +++ b/src/core/widgets/code/controllers/files_controller.py @@ -33,7 +33,8 @@ class FilesController(ControllerBase, list): def filter_loaded(self, event: Code_Event_Types.FilterOutLoadedFilesEvent): - loaded_paths = {file.fpath for file in self} + loaded_paths = {file.fpath for file in self if not file.fpath == "buffer"} + files = [ uri for uri in event.uris if not any(path in uri for path in loaded_paths) ] diff --git a/src/core/widgets/code/controllers/views/signal_mapper.py b/src/core/widgets/code/controllers/views/signal_mapper.py index f73e53d..915acf9 100644 --- a/src/core/widgets/code/controllers/views/signal_mapper.py +++ b/src/core/widgets/code/controllers/views/signal_mapper.py @@ -1,6 +1,9 @@ # Python imports # Lib imports +import gi + +from gi.repository import GLib # Application imports from ...source_view import SourceView @@ -21,6 +24,7 @@ class SourceViewSignalMapper: def set_buffer_to_active_view(self, buffer): self.active_view.set_buffer(buffer) self.active_view.command.exec("update_info_bar") + GLib.idle_add(self._scroll_to_iter) def connect_signals(self, source_view: SourceView): signal_mappings = self._get_signal_mappings() @@ -35,6 +39,12 @@ class SourceViewSignalMapper: def insert_text(self, file, string: str): return self.state_manager.handle_insert_text(self.active_view, file, string) + def _scroll_to_iter(self): + buffer = self.active_view.get_buffer() + itr = buffer.get_iter_at_mark( buffer.get_insert() ) + + self.active_view.scroll_to_iter(itr, 0.2, False, 0, 0) + def _get_signal_mappings(self): return { "focus-in-event": self._focus_in_event, diff --git a/src/core/widgets/code/controllers/views/source_views_controller.py b/src/core/widgets/code/controllers/views/source_views_controller.py index 94edc48..059f917 100644 --- a/src/core/widgets/code/controllers/views/source_views_controller.py +++ b/src/core/widgets/code/controllers/views/source_views_controller.py @@ -36,7 +36,9 @@ class SourceViewsController(ControllerBase, list): if not self.signal_mapper.active_view: return - if isinstance(event, Code_Event_Types.TextChangedEvent): + if isinstance(event, Code_Event_Types.GetActiveViewEvent): + event.response = self.signal_mapper.active_view + elif isinstance(event, Code_Event_Types.TextChangedEvent): self.signal_mapper.active_view.command.exec("update_info_bar") elif isinstance(event, Code_Event_Types.SetActiveFileEvent): self.signal_mapper.set_buffer_to_active_view(event.buffer) diff --git a/src/core/widgets/code/source_view.py b/src/core/widgets/code/source_view.py index 9dc749f..4f348cd 100644 --- a/src/core/widgets/code/source_view.py +++ b/src/core/widgets/code/source_view.py @@ -6,7 +6,6 @@ gi.require_version('Gtk', '3.0') gi.require_version('GtkSource', '4') from gi.repository import Gtk -from gi.repository import GLib from gi.repository import GtkSource # Application imports @@ -22,9 +21,6 @@ class SourceView(GtkSource.View, SourceViewDnDMixin): self.state = state - self._cut_temp_timeout_id = None - self._cut_buffer = "" - self.sibling_right = None self.sibling_left = None @@ -74,15 +70,3 @@ class SourceView(GtkSource.View, SourceViewDnDMixin): ) self._set_up_dnd() - - def clear_temp_cut_buffer_delayed(self): - if self._cut_temp_timeout_id: - GLib.source_remove(self._cut_temp_timeout_id) - - def set_temp_cut_buffer_delayed(self): - def clear_temp_buffer(): - self._cut_buffer = "" - self._cut_temp_timeout_id = None - return False - - self._cut_temp_timeout_id = GLib.timeout_add(15000, clear_temp_buffer) diff --git a/user_config/usr/share/app_name/code-key-bindings.json b/user_config/usr/share/app_name/code-key-bindings.json index e35ca74..7cecd01 100644 --- a/user_config/usr/share/app_name/code-key-bindings.json +++ b/user_config/usr/share/app_name/code-key-bindings.json @@ -15,18 +15,12 @@ "zoom_out": { "held": "minus" }, - "cut_to_temp_buffer": { - "held": "k" - }, "duplicate_line": { "held": "d" }, "go_to": { "released": "g" }, - "paste_temp_buffer": { - "held": "u" - }, "new_file": { "released": "t" },