32 Commits

Author SHA1 Message Date
0c3de1334a Fix line-based editing behavior for cut and duplicate commands
- Cut to temp buffer:
  - Respect selections by expanding to full line boundaries
  - Normalize end iterator to include full line + newline
  - Ensure consistent newline handling for last line
  - Preserve cursor position after delete
  - Prevent line merging when accumulating cut buffer

- Duplicate line:
  - Simplify logic using line-based iter APIs
  - Fix incorrect selection handling and off-by-one issues
  - Ensure full-line duplication for both selection and cursor cases
  - Correct cursor/selection restoration after duplication
2026-04-15 23:02:15 -05:00
41f3501e1f feat(code): improve comment toggling, terminal navigation, and editor event wiring
- Refactor Commenter toggle logic for line and multi-line comments
  - Preserve indentation and cursor position
  - Improve handling of existing comment detection and removal
  - Simplify bounds vs line comment dispatch

- Enhance terminal project navigation
  - Add project marker detection via Gio file traversal
  - Implement go-to-project-or-home behavior (Home key shortcut)
  - Automatically `cd` into detected project root or home directory
  - Wire terminal widget navigation through VteWidget

- Improve terminal integration
  - Pass emit_to into terminals view for event dispatching
  - Add ability for VteWidget to trigger project navigation

- Update split pane shortcut
  - Change close split view binding to Alt+\

- Add editor event support
  - Emit `text_insert` event from SourceFile on insert
  - Add new TextInsertEvent DTO and register in event system

- Misc cleanup
  - Improve imports and structure in terminals module
  - Add project marker list and filesystem traversal helpers
2026-04-15 01:54:56 -05:00
12b5fe7304 feat: improve LSP lifecycle, terminal widget, and code folding support
- LSP:
  - Add shutdown and exit request/notification handling
  - Send initialized notification after initialize response
  - Gracefully close clients with delayed shutdown via GLib timeout
  - Fix LSP WS server ↔ Godot LSP communication flow

- Terminal (VteWidget):
  - Switch to async spawn with full environment inheritance
  - Add PROMPT_COMMAND OSC7 support for cwd tracking
  - Improve UX: scrollback, no audible bell, auto scroll
  - Implement clipboard shortcuts, selection copy, middle-click paste
  - Track cwd changes and update UI label
  - Add proper signal wiring and cleanup on destroy

- Code folding:
  - Add fold support for JS, HTML, CSS, JSON, C, C++, Go
  - Reset fold state safely when AST or filetype is unavailable

- UI (Plugins dialog):
  - Improve dialog behavior (non-modal, centered, transient)
  - Add focus-out auto-hide and Ctrl+Shift+P shortcut

- Misc:
  - Add type hints in VTE widget
  - Update TODOs (remove completed items, add LSP comm fix)
  - Add terminal plugin scaffolding
2026-04-13 00:50:42 -05:00
d8e0185d1c Refactor LSP manager architecture and event + completion pipeline
- Replace legacy LSPManagerClient + LSPManagerUI with ClientManager and UIManager
- Remove WebsocketClient in favor of unified Websocket implementation
- Move LSP initialization config loading into centralized config module
- Update LSPClient to support dynamic socket, improved init params, and doc version tracking
- Introduce range-based didChange notifications and add implementation + references requests
- Expand LSPClientEvents to support implementation/references and structured range edits
- Simplify websocket response handling and normalize LSP response parsing flow
- Decouple UI from LSP manager core; UI now emits address/port for client creation
- Refactor completion provider pipeline:
  - Split TextChangedEvent into TextInsertedEvent and DeleteRangeEvent
  - Update ProviderResponseCacheBase and controller dispatch paths accordingly
- Improve SourceBuffer and SourceFile event tracking with delete-range support
- Update plugin system:
  - Centralize command/provider registration via helper methods
  - Add LSP commands: definition, references, implementation, toggle UI
- Enhance response handlers to support references and implementation hooks
- Improve Python LSP config (jedi completion, signatures, references enabled)
- Fix minor GTK lifecycle and buffer signal handling issues
- Clean up unused imports, dead code, and outdated JSON server configs
2026-04-11 15:32:42 -05:00
fd1f5b8d64 refactor: remove split_pane_manager plugin and harden view handling
- Delete deprecated split_pane_manager command plugin and all related commands
- Introduce new split_pane command structure (untracked replacement)
- Add guard in code_minimap to handle missing active view
- Prevent language detection for non-file buffers in command_helpers

This cleans up legacy split pane logic and improves stability for edge cases.
2026-04-04 23:19:35 -05:00
69e766dc28 feat(editor): introduce split pane manager plugin and refactor source view system
* address TODO item for split_pane_manager plugin and bound keys
* add split_pane_manager plugin with create/close split view commands
* move sibling focus/move commands from core into plugin system
* remove legacy toggle_source_view plugin
* redesign EditorsContainer to use simpler Box-based layout
* refactor source view API to return (scrolled_window, source_view)
* add source view lifecycle events (create/remove/removed)
* rename GetNewCommandSystemEvent → CreateCommandSystemEvent
* update CommandsController to support command system creation and cleanup
* ensure command systems are removed with their source views
* improve focus border handling across sibling chains
* update telescope integration for new source view structure
* clean up container imports and initialization logic
* remove old keybindings and align with new split pane workflow
2026-04-04 21:33:09 -05:00
5911f37449 refactor(command-system): replace legacy CommandSystem with SourceViewCommandSystem
- Remove CommandSystem and CommandSystemMixin
- Introduce SourceViewCommandSystem and shared libs/command_system
- Update CommandsController to use new command system
- Adjust package exports accordingly

feat(lsp): clean up and normalize LSP configurations

- Fix invalid JSON in Godot LSP config (processId -> null, formatting)
- Remove embedded jedi-language-server config from main Python LSP config
- Add separate jedi-lsp-server-config.json

refactor(markers): rename move_word -> move_along_word for clarity

- Update all usages in MarkerManager

chore: minor formatting and whitespace fixes
2026-04-04 14:18:32 -05:00
6e46279da4 refactor(cursor/multi-insert): unify movement logic and improve word navigation
* Extract `_proc_move` to centralize cursor and selection handling
* Rework multi-insert cursor movement and key handling (arrow/ctrl/shift/super)
* Add `ignore_leader` support to decouple leader cursor behavior
* Replace `move_word_snake_case` with improved `move_along_word` (fix '-' handling)
* Add `is_super` modifier support and clean up TODO
2026-03-30 00:36:52 -05:00
d90415bffc Fix: Improve code folding functionality and gutter rendering
- Updated folding actions and engine for more consistent behavior.
- Added helper function `is_fold_hidden()` to check fold visibility state.
- Improved gutter renderer to handle collapsible code blocks more reliably.
- Refined tag handling for invisible folds to prevent desync issues.
- Removed code fold Fix related entry in `TODO.md`.
2026-03-29 14:33:40 -05:00
62a866d9bb feat(tree-sitter, views): initialize AST on focus and emit source view creation event
- Add set_ast helper to centralize Tree-sitter parsing logic
- Parse and attach AST on FocusedViewEvent and TextChangedEvent
- Request file from buffer on view focus before parsing
- Fix parser guard condition in get_parser (handle missing language properly)

- Emit CreatedSourceViewEvent when a new source view is added
- Register CreatedSourceViewEvent in DTO exports

- Update TODO:
  - Remove completed collapsible code blocks task
  - Add fix note for code block icon desync issue

chore:
- Add scaffolding for code_fold UI plugin
- Add created_source_view_event DTO
2026-03-29 03:09:43 -05:00
dc2997ec16 feat(lsp, ui, core): refactor LSP initialization, improve config handling, and clean up TreeSitter
* LSP Client & Manager
  Refactored initialization flow to use internal state instead of passing params
  Added workspace_path and init_opts to LSPClient
  Simplified send_initialize_message() (no external args)
  Updated manager/client APIs to require explicit workspace_path and typed init_opts
  Improved client lifecycle handling and UI button toggling

* LSP Manager UI
  Added {user.home} substitution support in configs
  Use live editor buffer for JSON config parsing instead of static template
  Minor cleanup and formatting improvements

* Core Widgets
  Prevent external modification checks for buffer-only files in SourceFile
  Minor formatting cleanup in SourceView

* Plugins / Manifest
  Expanded manifest schema: added description and copyright, reordered fields

* Cleanup
  Removed TreeSitter compile script and TODO entry
  General code formatting and small consistency fixes

chore: remove unused TreeSitter tooling and related TODO entry
2026-03-28 16:14:04 -05:00
70877a7ee1 feat(event-watchers): rework file state detection and migrate tree-sitter integration
Switch file state checks to trigger on FocusedViewEvent instead of TextChangedEvent
Resolve file via files controller and operate directly on file object
Add user prompt for externally modified files with optional reload
Auto-reload unmodified buffers when external changes are detected
Simplify file_is_deleted / file_is_externally_modified APIs

tree-sitter

Remove bundled tree_sitter_language_pack and related setup/notes scripts
Introduce local lightweight tree_sitter wrapper (Parser, get_parser)
Store parser per file and generate AST directly from buffer text
Remove runtime language download/config logic
Update manifest (drop autoload)

core

Simplify CLI file handling in BaseControllerMixin
Send directory args directly via IPC instead of encoding in file list

cleanup

Remove unused libs, scripts, and legacy code paths
2026-03-27 20:00:59 -05:00
cb73f6b3b0 Fix unload lifecycle, widget cleanup, and plugin removal handling
- Rename GodotHandler to GDScriptHandler in LSP client
- Fix unload() method naming in nanoesq_temp_buffer plugin
- Return manifest_meta in manifest_manager for manual launch plugins
- Properly destroy tabs_widget, viewport, and scrolled_win on unload
- Refactor plugin removal to search all manifest lists and fix cleanup order
- Fix widget reference in plugins_ui (use child instead of box)
2026-03-23 23:05:20 -05:00
e6eaa1d83c Fixed file_history plugin after breakage from testing a pattern 2026-03-23 21:42:05 -05:00
62731ae766 Improve file loading robustness and drag-and-drop handling
- Add error handling for UTF-8 decoding to gracefully handle invalid bytes
- Refactor DnD target handling with explicit target list and proper context completion
- Remove completed TODO item for file open event emission
- Clean up memory by deleting file contents after decoding
2026-03-23 01:32:57 -05:00
b5cec0d049 Fix multi-select word movement for snake_case and add Ctrl+scroll zoom
- Implement snake_case-aware word movement that treats underscores as part of words
- Fix selection edge detection to only move when caret is at appropriate boundary
- Add INSERT state guards to line_up/down commands
- Add Ctrl+scroll zoom in/out functionality for text size
- Remove obsolete newton.zip archive
2026-03-22 23:42:28 -05:00
c821f30880 Added TODOs; made explicit that LSP Manager pre launch if autoload true/unset; added WIP tree_sitter plugin 2026-03-22 17:56:48 -05:00
13908d7ba7 Refactor file loading to detect external modifications and add reload capability
- Extract _load_data() method from load_path() for reuse in reload()
- Add is_extters externally_modified() to track file state changes (mtime/size)
- Replace load_bytes() with load_contents() for better error handling
- Add reload() method to re-read file from disk
- Fix file_state_watcher by properly detecting external changes
- Update TODO list (add Terminal plugin, mark file_state_watcher as fixed)
2026-03-22 00:35:11 -05:00
ec69d4db73 Addressed 'tabs_bar' TODO 2026-03-21 18:16:20 -05:00
511138316a Addressed Telescope TODO; enhanced Telescope search 2026-03-21 17:19:10 -05:00
d6e0823e21 Adding images to README file 2026-03-21 15:29:12 -05:00
54e7b58c24 Updated README 2026-03-21 14:16:23 -05:00
77a3b71d31 feat: Complete plugin lifecycle management with lazy loading and runtime reload
Major changes:
- Add unload() method to all plugins for proper cleanup (unregister commands/providers/LSP clients, destroy widgets, clear state)
- Implement lazy widget loading via "show" signal across all containers
- Add autoload: false manifest option for manual/conditional plugin loading
- Add Plugins UI with runtime load/unload toggle via Ctrl+Shift+p
- Implement controller unregistration system with proper signal disconnection
- Add new events: UnregisterCommandEvent, GetFilesEvent, GetSourceViewsEvent, TogglePluginsUiEvent
- Fix signal leaks by tracking and disconnecting handlers in widgets (search/replace, LSP manager, tabs, telescope, markdown preview)
- Add Save/Save As to tabs context menu
- Improve search/replace behavior (selection handling, focus management)
- Add telescope file initialization from existing loaded files
- Refactor plugin reload watcher to dynamically add/remove plugins on filesystem changes
- Add new plugins: file_history, extend_source_view_menu, godot_lsp_client
- Fix bug in prettify_json (undefined variable reference)
2026-03-21 13:22:20 -05:00
21dd86ad3d Add dynamic EventNamespace for event type access and refactor LSP manager initialization
- Create EventNamespace class to enable Code_Event_Types.RegisterLspClientEvent access
- Move set_event_hub call from plugin to lsp_manager._do_bind_mapping
- Update event type references to use Code_Event_Types namespace
2026-03-15 23:41:09 -05:00
fea303c898 Remove custom LSP manager plugins and add new language server clients
- Delete old lsp_manager plugin (custom websocket-based LSP client implementation)
- Delete java_lsp_client plugin
- Delete python_lsp_client plugin
- Remove unused LSP DTO files in src/libs/dto/code/lsp/
- Add new language_server_clients plugin directory
- Improve event_factory with register_events method
- Add PYTHONDONTWRITEBYTECODE to user config
- Update events init.py docstring
2026-03-15 20:47:40 -05:00
e8653cd116 refactor: remove LSPServerEventsMixin and clean up websocket tests
- Delete unused websocket library test files
- Remove LSPServerEventsMixin and inline its methods into response handlers
- Clean up unused imports (ThreadPoolExecutor, ABC, LSP message structs)
2026-03-15 03:36:01 -05:00
5e28fb1e5c refactor(lsp-manager): replace handler architecture with response registry and modular providers
* Remove legacy handlers system (BaseHandler, DefaultHandler, JavaHandler, PythonHandler, HandlerRegistry)
* Introduce response_handlers module with ResponseRegistry for LSP response routing
* Replace HandlerRegistry usage in LSPManager with ResponseRegistry
* Convert LSPManagerUI client lifecycle to GObject signals
* Remove GLib.idle_add usage in LSP client event dispatch
* Move completion request logic into LSPServerEventsMixin
* Replace provider.py and provider_response_cache.py with modular provider/ package
* Simplify plugin wiring via response_registry.set_event_hub()

This refactor decouples response handling, simplifies event flow, and prepares
the LSP manager for easier language-specific extensions.
2026-03-15 01:52:15 -05:00
609eaa8246 refactor(lsp): replace controller layer with client module and LSPManager orchestration
* Rename legacy controller subsystem (LSPController, websocket controller, controller events, and base classes)
    into clarified client module for LSP communication
* Structure around LSPManager and LSPManagerClient to handle orchestration and client lifecycle
* Update plugin integration to use LSPManager instead of LSPController
* Simplify architecture by reducing controller indirection
2026-03-12 00:07:59 -05:00
71bab687d7 refactor(lsp): replace LSPManager with controller-based architecture
- Remove legacy LSPManager dialog implementation
- Introduce LSPController as the central LSP entry point
- Route UI interactions through lsp_controller.lsp_manager_ui
- Move client lifecycle handling out of ProviderResponseCache
- Simplify completion cache and matcher filtering
- Improve LSP completion item parsing with safer fallbacks
- Modernize typing (Python 3.10 union syntax)
- Remove unused imports and dead code
- Use GLib.idle_add for safe UI scrolling operations
- Minor comment and spelling fixes
2026-03-11 23:17:57 -05:00
060f68237b feat(lsp): support Java class file contents and improve definition navigation handling
- Add `_lsp_java_class_file_contents` request to fetch contents of compiled Java classes via LSP (`java/classFileContents`).
- Handle `java/classFileContents` responses by opening a new buffer with Java syntax highlighting and inserting the returned source.
- Update definition handling to pass URI and range, enabling precise cursor placement after navigation.
- Detect `jdt://` URIs in `textDocument/definition` responses and request class file contents instead of direct navigation.
- Move goto navigation logic into `LSPServerEventsMixin`, using event system to access the active view and position the cursor.
- Expose `emit` and `emit_to` to the response cache for event dispatching.
- Restrict completion activation to `USER_REQUESTED`.
- Add TODO note about mapping language IDs to dedicated response handlers.
2026-03-08 17:53:12 -05:00
ee5f66fbbb Fix on load scroll pointer to view 2026-03-08 15:09:19 -05:00
7ee484f0c0 Remove temp cut buffer commands and cleanup related SourceView code
- Moved cut_to_temp_buffer and paste_temp_buffer commands to plugin
- Remove temp buffer state and timeout logic from SourceView
- Remove associated keybindings (Ctrl+K / Ctrl+U)
- Ignore "buffer" pseudo-path when filtering loaded files
- Ensure view receives focus after DnD file load
2026-03-08 14:51:11 -05:00
265 changed files with 6573 additions and 3680 deletions

View File

@@ -1,25 +1,13 @@
# Python-With-Gtk-Template
A template project for Python with Gtk applications.
### Requirements
* PyGObject (Gtk introspection library)
* pygobject-stubs (For actually getting pylsp or python-language-server to auto complete in LSPs. Do if GTK3 --no-cache-dir --config-settings=config=Gtk3,Gdk3,Soup2)
* pyxdg (Desktop ".desktop" file parser)
* setproctitle (Define process title to search and kill more easily)
* sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy)
# Newton
A Python + Gtk 3 based quasi-IDE.
### Note
* pyrightconfig.json can prompt IDEs that use pyright lsp on where imports are located- look at venvPath and venv. "venvPath" is parent path of "venv" where "venv" is just the name of the folder under the parent path that is the python created venv.
* Move respetive sub folder content under user_config to the same places in Linux. Though, user/share/<app name> can go to ~/.config folder if prefered.
* In additiion, place the plugins folder in the same app folder you moved to /usr/share/<app name> or ~/.config/<app name> .
There are a "\<change_me\>" strings and files that need to be set according to your app's name located at:
* \_\_builtins\_\_.py
* user_config/bin/app_name
* user_config/usr/share/app_name
* user_config/usr/share/app_name/icons/app_name.png
* user_config/usr/share/app_name/icons/app_name-64x64.png
* user_config/usr/share/applications/app_name.desktop
[TODO](TODO.md)
For the user_config, after changing names and files, copy all content to their respective destinations.
The logic follows Debian Dpkg packaging and its placement logic.
### Images
![1 Newton default view.](images/pic1.png)
![2 Newton split pane view.](images/pic2.png)
![3 Newton search and replace shown.](images/pic3.png)
![4 Newton displaying inline colors.](images/pic4.png)
![5 Newton as transparent with youtube playing below it.](images/pic5.png)
![6 Newton with plugins menu show as well as markdown preview shown.](images/pic6.png)

15
TODO.md Normal file
View File

@@ -0,0 +1,15 @@
___
### Add
1. Add <Ctrl\>i to **lsp_manager** to list who implements xyz
___
### Change
1. Make **telescope** plugin a generic base to allow query mode additions through plugins
___
### Fix
- Fix LSP WS Server to Godot LSP Server communication
- Fix <Ctrl\>z in multi-insert mode being funky. Insure updates happen on block level.
I.E, maybe push updates to queue to insure block undo/redo?
___

BIN
images/pic1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
images/pic2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
images/pic3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
images/pic4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
images/pic5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

BIN
images/pic6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 KiB

View File

@@ -39,6 +39,24 @@ class Plugin(PluginCode):
self.emit_to("source_views", event)
def unload(self):
event = Event_Factory.create_event("unregister_command",
command_name = "autopairs",
command = Handler,
binding_mode = "held",
binding = [
"'", "`", "[", "]",
'<Shift>"',
'<Shift>(',
'<Shift>)',
'<Shift>{',
'<Shift>}'
]
)
self.emit_to("source_views", event)
autopairs = None
def run(self):
...

View File

@@ -27,7 +27,19 @@ class Plugin(PluginCode):
colorize.handle_colorize(event.buffer)
def load(self):
event = Event_Factory.create_event("register_command",
self._manage_signals("register_command")
def unload(self):
self._manage_signals("unregister_command")
event = Event_Factory.create_event("get_source_views")
self.emit_to("source_views", event)
for view in event.response:
buffer = view.get_buffer()
colorize.clear_color_tags(buffer)
def _manage_signals(self, action: str):
event = Event_Factory.create_event(action,
command_name = "tggle_colorize",
command = Handler,
binding_mode = "released",
@@ -36,6 +48,7 @@ class Plugin(PluginCode):
self.emit_to("source_views", event)
def run(self):
...

View File

@@ -13,54 +13,95 @@ class Commenter(CodeCommentTagsMixin):
def keyboard_tggl_comment(self, buffer):
language = buffer.get_language()
if language is None: return
language = buffer.get_language()
if not language: return
start_tag, end_tag = self.get_comment_tags(language)
# Note: Only handling line comment tag- no block comment option
if not start_tag and not end_tag: return
if not (start_tag or end_tag): return
bounds = buffer.get_selection_bounds()
if bounds:
self._bounds_comment(
start_tag, end_tag, bounds, buffer
)
else:
self._line_comment(start_tag, end_tag, buffer)
start_tag += " "
end_tag = end_tag or ""
bounds = buffer.get_selection_bounds()
def _line_comment(self, start_tag, end_tag, buffer):
start_itr = buffer.get_iter_at_mark( buffer.get_insert() ).copy()
end_itr = start_itr.copy()
if not start_itr.starts_line():
start_itr.set_line_offset(0)
if not end_itr.ends_line():
end_itr.forward_to_line_end()
text = buffer.get_text(start_itr, end_itr, True)
text = text.replace(start_tag, "") if text.startswith(start_tag) else start_tag + text
buffer.begin_user_action()
buffer.delete(start_itr, end_itr)
buffer.insert(start_itr, text)
buffer.end_user_action()
def _bounds_comment(self, start_tag, end_tag, bounds, buffer):
start_itr, end_itr = bounds
if not start_itr.starts_line():
start_itr.set_line_offset(0)
if not end_itr.ends_line():
end_itr.forward_to_line_end()
text = buffer.get_text(start_itr, end_itr, True)
text = "\n".join(
line.replace(start_tag, "") if line.startswith(start_tag) else start_tag + line
for line in text.splitlines()
(self._bounds_comment if bounds else self._line_comment)(
buffer, start_tag, end_tag, bounds
)
def _line_comment(self, buffer, start_tag: str, end_tag: str, bounds):
start = buffer.get_iter_at_mark(buffer.get_insert()).copy()
end = start.copy()
line, col = start.get_line() + 1, start.get_line_offset()
if not start.starts_line():
start.set_line_offset(0)
if not end.ends_line():
end.forward_to_line_end()
text = buffer.get_text(start, end, True)
stripped = text.lstrip()
indent = text[:-len(stripped)] if stripped else text
if stripped.startswith(start_tag):
stripped = stripped[len(start_tag):].lstrip().replace(end_tag, "", 1)
else:
stripped = f"{start_tag}{stripped}{end_tag}"
buffer.begin_user_action()
buffer.delete(start_itr, end_itr)
buffer.insert(start_itr, text)
buffer.delete(start, end)
buffer.insert(start, indent + stripped)
buffer.end_user_action()
buffer.place_cursor(buffer.get_iter_at_line_offset(line, col))
def _bounds_comment(self, buffer, start_tag: str, end_tag: str, bounds):
def indent_len(s): return len(s) - len(s.lstrip())
def insert(line, idx):
return f"{line[:idx]}{start_tag}{line[idx:]}{end_tag}"
def process(lines):
base_indent = min(
(indent_len(l) for l in lines if l.strip()),
default = 0
)
is_commented = all(
l.lstrip().startswith(start_tag)
for l in lines if l.strip()
)
if is_commented:
return [
l.replace(start_tag, "", 1).replace(end_tag, "", 1)
if l.lstrip().startswith(start_tag.lstrip())
else l
for l in lines
]
return [
l if not l.strip()
else insert(l, base_indent)
for l in lines
]
start, end = bounds
sline, scol = start.get_line(), start.get_line_offset()
eline, ecol = end.get_line(), end.get_line_offset()
if not start.starts_line():
start.set_line_offset(0)
if not end.ends_line():
end.forward_to_line_end()
lines = buffer.get_text(start, end, True).splitlines()
new_text = "\n".join(process(lines))
buffer.begin_user_action()
buffer.delete(start, end)
buffer.insert(start, new_text)
buffer.end_user_action()
buffer.select_range(
buffer.get_iter_at_line_offset(sline, scol),
buffer.get_iter_at_line_offset(eline, ecol),
)

View File

@@ -36,6 +36,16 @@ class Plugin(PluginCode):
self.emit_to("source_views", event)
def unload(self):
event = Event_Factory.create_event("unregister_command",
command_name = "keyboard_tggl_comment",
command = Handler,
binding_mode = "released",
binding = "<Control>slash"
)
self.emit_to("source_views", event)
def run(self):
...

View File

@@ -0,0 +1,97 @@
# Python imports
# Lib imports
# Application imports
class Autopairs:
def __init__(self):
...
def handle_word_wrap(self, buffer, char_str: str):
wrap_block = self.get_wrap_block(char_str)
if not wrap_block: return
selection = buffer.get_selection_bounds()
if not selection:
self.insert_pair(buffer, char_str, wrap_block)
return True
self.wrap_selection(buffer, char_str, wrap_block, selection)
return True
def insert_pair(
self, buffer, char_str: str, wrap_block: tuple
):
buffer.begin_user_action()
left_block, right_block = wrap_block
insert_mark = buffer.get_insert()
insert_itr = buffer.get_iter_at_mark(insert_mark)
buffer.insert(insert_itr, f"{left_block}{right_block}")
insert_itr = buffer.get_iter_at_mark( insert_mark )
insert_itr.backward_char()
buffer.place_cursor(insert_itr)
buffer.end_user_action()
def wrap_selection(
self, buffer, char_str: str, wrap_block: tuple, selection
):
left_block, \
right_block = wrap_block
start_itr, \
end_itr = selection
data = buffer.get_text(
start_itr, end_itr, include_hidden_chars = False
)
start_mark = buffer.create_mark("startclose", start_itr, False)
end_mark = buffer.create_mark("endclose", end_itr, True)
buffer.begin_user_action()
buffer.insert(start_itr, left_block)
end_itr = buffer.get_iter_at_mark(end_mark)
buffer.insert(end_itr, right_block)
start = buffer.get_iter_at_mark(start_mark)
end = buffer.get_iter_at_mark(end_mark)
buffer.select_range(start, end)
buffer.delete_mark_by_name("startclose")
buffer.delete_mark_by_name("endclose")
buffer.end_user_action()
def get_wrap_block(self, char_str) -> tuple:
left_block = ""
right_block = ""
match char_str:
case "(" | ")":
left_block = "("
right_block = ")"
case "[" | "]":
left_block = "["
right_block = "]"
case "{" | "}":
left_block = "{"
right_block = "}"
case '"':
left_block = '"'
right_block = '"'
case "'":
left_block = "'"
right_block = "'"
case "`":
left_block = "`"
right_block = "`"
case _:
return ()
return left_block, right_block

View File

@@ -1,5 +1,5 @@
{
"name": "Toggle Source View",
"name": "File History",
"author": "ITDominator",
"version": "0.0.1",
"support": "",

View File

@@ -0,0 +1,65 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
history: list = []
history_size: int = 30
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.RemovedFileEvent):
if event.file.ftype == "buffer": return
if len(history) == history_size:
history.pop(0)
history.append(event.file.fpath)
def load(self):
self._manage_signals("register_command")
def unload(self):
self._manage_signals("unregister_command")
def _manage_signals(self, action: str):
event = Event_Factory.create_event(action,
command_name = "file_history_pop",
command = Handler,
binding_mode = "released",
binding = "<Shift><Control>t"
)
self.emit_to("source_views", event)
def run(self):
...
class Handler:
@staticmethod
def execute(
view: any,
char_str: str,
*args,
**kwargs
):
logger.debug("Command: File History")
if len(history) == 0: return
view._on_uri_data_received(
[
f"file://{history.pop()}"
]
)

View File

@@ -0,0 +1,57 @@
# 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()
if buffer.get_has_selection():
start_itr, end_itr = buffer.get_selection_bounds()
start_itr.set_line_offset(0)
if not end_itr.ends_line():
end_itr.forward_to_line_end()
if not end_itr.is_end():
end_itr.forward_char()
else:
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 = ""
text = buffer.get_text(start_itr, end_itr, True)
if not text.endswith("\n"):
text += "\n"
view._cut_buffer += text
buffer.delete(start_itr, end_itr)
buffer.place_cursor(start_itr)
set_temp_cut_buffer_delayed(view)

View File

@@ -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)

View File

@@ -0,0 +1,7 @@
{
"name": "Nanoesq Temp Buffer",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {}
}

View File

@@ -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)

View File

@@ -0,0 +1,49 @@
# 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):
self._manage_signals("register_command")
def unload(self):
self._manage_signals("unregister_command")
def _manage_signals(self, action: str):
event = Event_Factory.create_event(action,
command_name = "cut_to_temp_buffer",
command = Handler,
binding_mode = "held",
binding = "<Control>k"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "paste_temp_buffer",
command = Handler2,
binding_mode = "held",
binding = "<Control>u"
)
self.emit_to("source_views", event)
def run(self):
...

View File

@@ -0,0 +1,3 @@
"""
Pligin Module
"""

View File

@@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@@ -0,0 +1,77 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import Gtk
from gi.repository import GtkSource
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
emit_to: callable = None
def get_source_view(widget):
if isinstance(widget, GtkSource.View):
return widget
if isinstance(widget, Gtk.ScrolledWindow):
return widget.get_child()
if isinstance(widget, Gtk.Paned):
return get_source_view(widget.get_child1())
return None
def execute(
source_view,
char_str,
modkeys_states
):
logger.debug("Command: Close Split Pane")
scrolled_win = source_view.get_parent()
pane = scrolled_win.get_parent()
if not isinstance(pane, Gtk.Paned): return
container = pane.get_parent()
source_view1 = pane.get_child1()
source_view2 = pane.get_child2()
if scrolled_win == source_view1:
remaining = source_view2
closing_view = source_view
else:
remaining = source_view1
closing_view = source_view
remaining_view = get_source_view(remaining)
left = closing_view.sibling_left
right = closing_view.sibling_right
if left:
left.sibling_right = right
if right:
right.sibling_left = left
pane.remove(source_view1)
pane.remove(source_view2)
container.remove(pane)
container.add(remaining)
event = Event_Factory.create_event(
"remove_source_view",
view = closing_view
)
emit_to("source_views", event)
remaining_view.grab_focus()

View File

@@ -0,0 +1,77 @@
# Python imports
# Lib imports
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from libs.dto.states import SourceViewStates
emit_to: callable = None
def execute(
source_view1,
char_str,
modkeys_states
):
logger.debug("Command: Split Pane")
scrolled_win1 = source_view1.get_parent()
container = scrolled_win1.get_parent()
pane = Gtk.Paned()
event = Event_Factory.create_event(
"create_source_view",
state = SourceViewStates.INSERT
)
emit_to("source_views", event)
scrolled_win2, \
source_view2 = event.response
old_sibling_right = None
if source_view1.sibling_right:
old_sibling_right = source_view1.sibling_right
source_view1.sibling_right = source_view2
if old_sibling_right:
old_sibling_right.sibling_left = source_view2
source_view2.sibling_right = old_sibling_right
source_view2.sibling_left = source_view1
pane.set_hexpand(True)
pane.set_vexpand(True)
pane.set_wide_handle(True)
container.remove(scrolled_win1)
pane.pack1( scrolled_win1, True, True )
pane.pack2( scrolled_win2, True, True )
container.add(pane)
def _show(pane, alloc, is_vertical: bool):
if is_vertical:
pane.set_position(alloc.width / 2)
else:
pane.set_position(alloc.height / 2)
pane.disconnect(pane.show_id)
is_control, is_shift, is_alt = modkeys_states
alloc = container.get_allocation()
if char_str == "|":
pane.show_id = pane.connect("show", _show, alloc, True)
pane.set_orientation(Gtk.Orientation.VERTICAL)
elif char_str == "\\":
pane.show_id = pane.connect("show", _show, alloc, False)
pane.set_orientation(Gtk.Orientation.HORIZONTAL)
pane.show_all()
source_view2.command.exec("new_file")
source_view2.grab_focus()

View File

@@ -7,6 +7,7 @@ gi.require_version('GtkSource', '4')
from gi.repository import GtkSource
# Application imports
@@ -18,5 +19,4 @@ def execute(
):
logger.debug("Command: Focus Left Sibling")
if not view.sibling_left: return
view.sibling_left.get_parent().show()
view.sibling_left.grab_focus()

View File

@@ -18,5 +18,4 @@ def execute(
):
logger.debug("Command: Focus Right Sibling")
if not view.sibling_right: return
view.sibling_right.get_parent().show()
view.sibling_right.grab_focus()

View File

@@ -1,5 +1,5 @@
{
"name": "LSP Manager",
"name": "Split Pane",
"author": "ITDominator",
"version": "0.0.1",
"support": "",

View File

@@ -0,0 +1,100 @@
# Python imports
# Lib imports
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# Application imports
from plugins.plugin_types import PluginCode
from libs.event_factory import Event_Factory, Code_Event_Types
from . import create_split_view, \
close_split_view, \
focus_left_sibling, \
focus_right_sibling, \
move_to_left_sibling, \
move_to_right_sibling
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
gemit_to = self.emit_to
self._manage_signals("register_command")
def unload(self):
self._manage_signals("unregister_command")
def _manage_signals(self, action: str):
_create_split_view = create_split_view
_close_split_view = close_split_view
_create_split_view.emit_to = self.emit_to
_close_split_view.emit_to = self.emit_to
event = Event_Factory.create_event(action,
command_name = "create_split_view",
command = _create_split_view,
binding_mode = "released",
binding = ["<Control>\\", "<Shift><Control>|"]
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "close_split_view",
command = _close_split_view,
binding_mode = "released",
binding = "<Alt>\\"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "focus_left_sibling",
command = focus_left_sibling,
binding_mode = "released",
binding = "<Control>Page_Up"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "focus_right_sibling",
command = focus_right_sibling,
binding_mode = "released",
binding = "<Control>Page_Down"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "move_to_left_sibling",
command = move_to_left_sibling,
binding_mode = "released",
binding = "<Control><Shift>Up"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "move_to_right_sibling",
command = move_to_right_sibling,
binding_mode = "released",
binding = "<Control><Shift>Down"
)
self.emit_to("source_views", event)
def run(self):
...

View File

@@ -1,54 +0,0 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
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 = "toggle_source_view",
command = Handler,
binding_mode = "released",
binding = "<Shift><Control>h"
)
self.emit_to("source_views", event)
def run(self):
...
class Handler:
@staticmethod
def execute(
view: any,
char_str: str,
*args,
**kwargs
):
logger.debug("Command: Toggle Source View")
target = view.get_parent()
target.hide() if target.is_visible() else target.show()
if view.sibling_left:
target = view.sibling_left.get_parent()
target.show()
view.sibling_left.grab_focus()
if view.sibling_right:
target = view.sibling_right.get_parent()
target.show()
view.sibling_right.grab_focus()

View File

@@ -35,5 +35,15 @@ class Plugin(PluginCode):
)
self.emit_to("completion", event)
def unload(self):
event = Event_Factory.create_event(
"unregister_provider",
provider_name = "Example Completer"
)
self.emit_to("completion", event)
self.provider = None
del self.provider
def run(self):
...

View File

@@ -51,7 +51,10 @@ class ProviderResponseCache(ProviderResponseCacheBase):
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
...
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
...
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
...
def filter(self, word: str) -> list[dict]:

View File

@@ -35,5 +35,15 @@ class Plugin(PluginCode):
)
self.emit_to("completion", event)
def unload(self):
event = Event_Factory.create_event(
"register_provider",
provider_name = "Python Completer"
)
self.emit_to("completion", event)
self.provider = None
del self.provider
def run(self):
...

View File

@@ -38,7 +38,7 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
iter.backward_char()
ch = iter.get_char()
# NOTE: Look to re-add or apply supprting logic to use spaces
# NOTE: Look to re-add or apply supporting logic to use spaces
# As is it slows down the editor in certain contexts...
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
if not (ch in ('_', '.') or ch.isalnum()):

View File

@@ -50,7 +50,10 @@ class ProviderResponseCache(ProviderResponseCacheBase):
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
...
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
...
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
...
def filter(self, word: str) -> list[dict]:

View File

@@ -35,5 +35,15 @@ class Plugin(PluginCode):
)
self.emit_to("completion", event)
def unload(self):
event = Event_Factory.create_event(
"unregister_provider",
provider_name = "Snippets Completer"
)
self.emit_to("completion", event)
self.provider = None
del self.provider
def run(self):
...

View File

@@ -49,7 +49,10 @@ class ProviderResponseCache(ProviderResponseCacheBase):
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
...
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
...
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
...
def filter(self, word: str) -> list[dict]:

View File

@@ -35,5 +35,15 @@ class Plugin(PluginCode):
)
self.emit_to("completion", event)
def unload(self):
event = Event_Factory.create_event(
"unregister_provider",
provider_name = "Words Completer"
)
self.emit_to("completion", event)
self.provider = None
del self.provider
def run(self):
...

View File

@@ -35,11 +35,14 @@ class ProviderResponseCache(ProviderResponseCacheBase):
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
...
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
buffer = event.file.buffer
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
buffer = event.buffer
self._clear_temp_delay()
self._set_temp_delay(buffer)
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
...
def _clear_temp_delay(self):
if self._temp_timeout_id:
GLib.source_remove(self._temp_timeout_id)

View File

@@ -0,0 +1,3 @@
"""
Plugin Module
"""

View File

@@ -0,0 +1,3 @@
"""
Plugin Package
"""

View File

@@ -0,0 +1,7 @@
{
"name": "Extend Source View Menu",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {}
}

View File

@@ -0,0 +1,29 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .source_view_menu import extend_source_view_menu
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.PopulateSourceViewPopupEvent):
extend_source_view_menu(event.buffer, event.menu)
def load(self):
...
def unload(self):
...
def run(self):
...

View File

@@ -0,0 +1,68 @@
# Python imports
import json
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
def on_case_handle(menuitem, buffer, action):
start_itr, \
end_itr = buffer.get_selection_bounds()
data = buffer.get_text(start_itr, end_itr, False)
text = data
if action == "on_all_upper":
text = data.upper()
elif action == "on_all_lower":
text = data.lower()
elif action == "on_invert":
text = data.swapcase()
elif action == "on_title":
text = data.title()
elif action == "on_title_strip":
text = data.title().replace("-", "").replace("_", "").replace(" ", "")
buffer.begin_user_action()
buffer.delete(start_itr, end_itr)
buffer.insert(start_itr, text)
buffer.end_user_action()
def extend_source_view_menu(buffer, menu):
if not buffer.get_selection_bounds(): return
for child in menu.get_children():
if not child.get_label() == "C_hange Case": continue
menu.remove(child)
change_case_item = Gtk.MenuItem(label = "Change Case")
case_menu = Gtk.Menu()
au_case_item = Gtk.MenuItem(label = "All Upper Case")
al_case_item = Gtk.MenuItem(label = "All Lower Case")
inver_case_item = Gtk.MenuItem(label = "Invert Case")
title_case_item = Gtk.MenuItem(label = "Title Case")
title_strip_case_item = Gtk.MenuItem(label = "Title Strip Case")
au_case_item.connect("activate", on_case_handle, buffer, "on_all_upper")
al_case_item.connect("activate", on_case_handle, buffer, "on_all_lower")
inver_case_item.connect("activate", on_case_handle, buffer, "on_invert")
title_case_item.connect("activate", on_case_handle, buffer, "on_title")
title_strip_case_item.connect("activate", on_case_handle, buffer, "on_title_strip")
case_menu.append(au_case_item)
case_menu.append(al_case_item)
case_menu.append(inver_case_item)
case_menu.append(title_case_item)
case_menu.append(title_strip_case_item)
change_case_item.set_submenu(case_menu)
menu.append(change_case_item)

View File

@@ -17,16 +17,28 @@ class Plugin(PluginCode):
def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.TextChangedEvent):
event.file.check_file_on_disk()
if not isinstance(event, Code_Event_Types.FocusedViewEvent): return
event = Event_Factory.create_event(
"get_file", buffer = event.view.get_buffer()
)
self.emit_to("files", event)
if event.file.is_deleted():
file_is_deleted(event)
elif event.file.is_externally_modified():
file_is_externally_modified(event)
file = event.response
if not file: return
if file.ftype == "buffer": return
file.check_file_on_disk()
if file.is_deleted():
file_is_deleted(file, self.emit)
elif file.is_externally_modified():
file_is_externally_modified(file, self.emit)
def load(self):
...
def unload(self):
...
def run(self):
...

View File

@@ -10,23 +10,45 @@ from libs.event_factory import Event_Factory, Code_Event_Types
def file_is_deleted(event):
event.file.was_deleted = True
def ask_yes_no(message):
dialog = Gtk.MessageDialog(
parent = None,
flags = 0,
message_type = Gtk.MessageType.QUESTION,
buttons = Gtk.ButtonsType.YES_NO,
text = message,
)
dialog.set_title("Confirm")
response = dialog.run()
dialog.destroy()
return response == Gtk.ResponseType.YES
def file_is_deleted(file, emit):
file.was_deleted = True
event = Event_Factory.create_event(
"file_externally_deleted",
file = event.file,
buffer = event.buffer
file = file,
buffer = file.buffer
)
self.emit(event)
emit(event)
def file_is_externally_modified(event):
# event = Event_Factory.create_event(
# "file_externally_modified",
# file = event.file,
# buffer = event.buffer
# )
# self.emit(event)
def file_is_externally_modified(file, emit):
event = Event_Factory.create_event(
"file_externally_modified",
file = file,
buffer = file.buffer
)
emit(event)
...
if not file.buffer.get_modified():
file.reload()
return
result = ask_yes_no("File has been externally modified. Reload?")
if not result: return
file.reload()

View File

@@ -32,5 +32,8 @@ class Plugin(PluginCode):
def load(self):
...
def unload(self):
...
def run(self):
...

View File

@@ -13,8 +13,6 @@ from gi.repository import Gtk
def add_prettify_json(buffer, menu):
menu.append( Gtk.SeparatorMenuItem() )
def on_prettify_json(menuitem, buffer):
start_itr, \
end_itr = buffer.get_start_iter(), buffer.get_end_iter()
@@ -28,4 +26,4 @@ def add_prettify_json(buffer, menu):
item = Gtk.MenuItem(label = "Prettify JSON")
item.connect("activate", on_prettify_json, buffer)
menu.append(item)
menu.append(item)

View File

@@ -0,0 +1,3 @@
"""
Pligin Module
"""

View File

@@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@@ -0,0 +1,134 @@
#!/bin/bash
# . CONFIG.sh
# set -o xtrace ## To debug scripts
# set -o errexit ## To exit on error
# set -o errunset ## To exit if a variable is referenced but not set
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TOOLS="$ROOT/.tools"
NODE_DIR="$TOOLS/node"
GRAMMARS_DIR="$ROOT/grammars"
BUILD_DIR="$ROOT/build"
OUTPUT="$ROOT/compiled"
NODE_VERSION="v24.14.1"
NODE_DIST="node-$NODE_VERSION-linux-x64"
NODE_ARCHIVE="$TOOLS/node.tar.xz"
NODE_URL="https://nodejs.org/dist/$NODE_VERSION/$NODE_DIST.tar.xz"
TS_CLI_VERSION="0.22.6"
LANGS=(
tree-sitter-python
tree-sitter-javascript
tree-sitter-html
tree-sitter-css
tree-sitter-json
tree-sitter-java
tree-sitter-c
tree-sitter-cpp
tree-sitter-go
)
REPOS=(
https://github.com/tree-sitter/tree-sitter-python
https://github.com/tree-sitter/tree-sitter-javascript
https://github.com/tree-sitter/tree-sitter-html
https://github.com/tree-sitter/tree-sitter-css
https://github.com/tree-sitter/tree-sitter-json
https://github.com/tree-sitter/tree-sitter-java
https://github.com/tree-sitter/tree-sitter-c
https://github.com/tree-sitter/tree-sitter-cpp
https://github.com/tree-sitter/tree-sitter-go
)
mkdir -p "$TOOLS" "$GRAMMARS_DIR" "$BUILD_DIR" "$OUTPUT"
ensure_node() {
if [ -x "$NODE_DIR/bin/node" ]; then
echo "==> Using cached Node.js"
return
fi
echo "==> Downloading Node.js $NODE_VERSION"
wget -O "$NODE_ARCHIVE" "$NODE_URL"
echo "==> Extracting Node.js"
mkdir -p "$NODE_DIR"
tar -xf "$NODE_ARCHIVE" -C "$NODE_DIR" --strip-components=1
rm "$NODE_ARCHIVE"
echo "==> Node installed at $NODE_DIR"
}
ensure_tree_sitter() {
export PATH="$NODE_DIR/bin:$PATH"
TS="$TOOLS/node_modules/.bin/tree-sitter"
if [ -x "$TS" ]; then
echo "==> Using cached tree-sitter-cli"
return
fi
echo "==> Installing tree-sitter-cli"
cd "$TOOLS"
npm init -y >/dev/null 2>&1 || true
npm install tree-sitter-cli@$TS_CLI_VERSION
}
sync_grammars() {
echo "==> Syncing grammars"
for i in "${!LANGS[@]}"; do
NAME="${LANGS[$i]}"
REPO="${REPOS[$i]}"
TARGET="$GRAMMARS_DIR/$NAME"
if [ -d "$TARGET/.git" ]; then
echo "Updating $NAME"
git -C "$TARGET" pull --depth 1
else
echo "Cloning $NAME"
git clone --depth 1 "$REPO" "$TARGET"
fi
done
}
build_lib() {
echo "==> Building Tree-sitter library"
mkdir -p "$OUTPUT"
PARSER_SRC=()
INCLUDE_PATHS=()
for GRAMMAR in "${LANGS[@]/#/$GRAMMARS_DIR/}"; do
echo "==> Processing grammar $GRAMMAR"
PARSER_SRC+=("$GRAMMAR/src/parser.c")
if [[ -f "$GRAMMAR/src/scanner.c" ]]; then
PARSER_SRC+=("$GRAMMAR/src/scanner.c")
fi
INCLUDE_PATHS+=("-I$GRAMMAR/src")
done
gcc -shared -o "$OUTPUT/languages.so" "${PARSER_SRC[@]}" "${INCLUDE_PATHS[@]}" -fPIC
echo "==> Output: $OUTPUT/languages.so"
}
main() {
ensure_node
ensure_tree_sitter
sync_grammars
build_lib
}
main "$@"

View File

@@ -0,0 +1,3 @@
"""
Libs Module
"""

View File

@@ -0,0 +1,71 @@
"""Python bindings to the Tree-sitter parsing library."""
from typing import Protocol as _Protocol
from ._binding import (
Language,
LogType,
LookaheadIterator,
Node,
Parser,
Point,
Query,
QueryCursor,
QueryError,
Range,
Tree,
TreeCursor,
LANGUAGE_VERSION,
MIN_COMPATIBLE_LANGUAGE_VERSION,
)
LogType.__doc__ = "The type of a log message."
Point.__doc__ = "A position in a multi-line text document, in terms of rows and columns."
Point.row.__doc__ = "The zero-based row of the document."
Point.column.__doc__ = "The zero-based column of the document."
class QueryPredicate(_Protocol):
"""A custom query predicate that runs on a pattern."""
def __call__(self, predicate, args, pattern_index, captures):
"""
Parameters
----------
predicate : str
The name of the predicate.
args : list[tuple[str, typing.Literal['capture', 'string']]]
The arguments to the predicate.
pattern_index : int
The index of the pattern within the query.
captures : dict[str, list[Node]]
The captures contained in the pattern.
Returns
-------
``True`` if the predicate matches, ``False`` otherwise.
Tip
---
You don't need to create an actual class, just a function with this signature.
"""
__all__ = [
"Language",
"LogType",
"LookaheadIterator",
"Node",
"Parser",
"Point",
"Query",
"QueryCursor",
"QueryError",
"QueryPredicate",
"Range",
"Tree",
"TreeCursor",
"LANGUAGE_VERSION",
"MIN_COMPATIBLE_LANGUAGE_VERSION",
]

View File

@@ -0,0 +1,416 @@
from enum import IntEnum
from collections.abc import ByteString, Callable, Iterator, Sequence
from typing import Annotated, Any, Final, Literal, NamedTuple, Protocol, Self, final, overload
from typing_extensions import deprecated
class _SupportsFileno(Protocol):
def fileno(self) -> int: ...
class Point(NamedTuple):
row: int
column: int
class LogType(IntEnum):
PARSE: int
LEX: int
@final
class Language:
@overload
@deprecated("int argument support is deprecated")
def __init__(self, ptr: Annotated[int, "TSLanguage *"], /) -> None: ...
@overload
def __init__(self, ptr: Annotated[object, "TSLanguage *"], /) -> None: ...
@property
def name(self) -> str | None: ...
@property
def abi_version(self) -> int: ...
@property
def semantic_version(self) -> tuple[int, int, int] | None: ...
@deprecated("Use abi_version instead")
@property
def version(self) -> int: ...
@property
def node_kind_count(self) -> int: ...
@property
def parse_state_count(self) -> int: ...
@property
def field_count(self) -> int: ...
@property
def supertypes(self) -> tuple[int, ...]: ...
def subtypes(self, supertype: int, /) -> tuple[int, ...]: ...
def node_kind_for_id(self, id: int, /) -> str | None: ...
def id_for_node_kind(self, kind: str, named: bool, /) -> int | None: ...
def node_kind_is_named(self, id: int, /) -> bool: ...
def node_kind_is_visible(self, id: int, /) -> bool: ...
def node_kind_is_supertype(self, id: int, /) -> bool: ...
def field_name_for_id(self, field_id: int, /) -> str | None: ...
def field_id_for_name(self, name: str, /) -> int | None: ...
def next_state(self, state: int, id: int, /) -> int: ...
def lookahead_iterator(self, state: int, /) -> LookaheadIterator | None: ...
@deprecated("Use the Query() constructor instead")
def query(self, source: str, /) -> Query: ...
def copy(self) -> Language: ...
def __repr__(self) -> str: ...
def __eq__(self, other: Any, /) -> bool: ...
def __ne__(self, other: Any, /) -> bool: ...
def __hash__(self) -> int: ...
def __copy__(self) -> Language: ...
@final
class Node:
@property
def id(self) -> int: ...
@property
def kind_id(self) -> int: ...
@property
def grammar_id(self) -> int: ...
@property
def grammar_name(self) -> str: ...
@property
def type(self) -> str: ...
@property
def is_named(self) -> bool: ...
@property
def is_extra(self) -> bool: ...
@property
def has_changes(self) -> bool: ...
@property
def has_error(self) -> bool: ...
@property
def is_error(self) -> bool: ...
@property
def parse_state(self) -> int: ...
@property
def next_parse_state(self) -> int: ...
@property
def is_missing(self) -> bool: ...
@property
def start_byte(self) -> int: ...
@property
def end_byte(self) -> int: ...
@property
def byte_range(self) -> tuple[int, int]: ...
@property
def range(self) -> Range: ...
@property
def start_point(self) -> Point: ...
@property
def end_point(self) -> Point: ...
@property
def children(self) -> list[Node]: ...
@property
def child_count(self) -> int: ...
@property
def named_children(self) -> list[Node]: ...
@property
def named_child_count(self) -> int: ...
@property
def parent(self) -> Node | None: ...
@property
def next_sibling(self) -> Node | None: ...
@property
def prev_sibling(self) -> Node | None: ...
@property
def next_named_sibling(self) -> Node | None: ...
@property
def prev_named_sibling(self) -> Node | None: ...
@property
def descendant_count(self) -> int: ...
@property
def text(self) -> bytes | None: ...
def walk(self) -> TreeCursor: ...
def edit(
self,
start_byte: int,
old_end_byte: int,
new_end_byte: int,
start_point: Point | tuple[int, int],
old_end_point: Point | tuple[int, int],
new_end_point: Point | tuple[int, int],
) -> None: ...
def child(self, index: int, /) -> Node | None: ...
def named_child(self, index: int, /) -> Node | None: ...
def first_child_for_byte(self, byte: int, /) -> Node | None: ...
def first_named_child_for_byte(self, byte: int, /) -> Node | None: ...
def child_by_field_id(self, id: int, /) -> Node | None: ...
def child_by_field_name(self, name: str, /) -> Node | None: ...
def child_with_descendant(self, descendant: Node, /) -> Node | None: ...
def children_by_field_id(self, id: int, /) -> list[Node]: ...
def children_by_field_name(self, name: str, /) -> list[Node]: ...
def field_name_for_child(self, child_index: int, /) -> str | None: ...
def field_name_for_named_child(self, child_index: int, /) -> str | None: ...
def descendant_for_byte_range(
self,
start_byte: int,
end_byte: int,
/,
) -> Node | None: ...
def named_descendant_for_byte_range(
self,
start_byte: int,
end_byte: int,
/,
) -> Node | None: ...
def descendant_for_point_range(
self,
start_point: Point | tuple[int, int],
end_point: Point | tuple[int, int],
/,
) -> Node | None: ...
def named_descendant_for_point_range(
self,
start_point: Point | tuple[int, int],
end_point: Point | tuple[int, int],
/,
) -> Node | None: ...
def __repr__(self) -> str: ...
def __str__(self) -> str: ...
def __eq__(self, other: Any, /) -> bool: ...
def __ne__(self, other: Any, /) -> bool: ...
def __hash__(self) -> int: ...
@final
class Tree:
@property
def root_node(self) -> Node: ...
@property
def included_ranges(self) -> list[Range]: ...
@property
def language(self) -> Language: ...
def root_node_with_offset(
self,
offset_bytes: int,
offset_extent: Point | tuple[int, int],
/,
) -> Node | None: ...
def copy(self) -> Tree: ...
def edit(
self,
start_byte: int,
old_end_byte: int,
new_end_byte: int,
start_point: Point | tuple[int, int],
old_end_point: Point | tuple[int, int],
new_end_point: Point | tuple[int, int],
) -> None: ...
def walk(self) -> TreeCursor: ...
def changed_ranges(self, new_tree: Tree, /) -> list[Range]: ...
def print_dot_graph(self, file: _SupportsFileno, /) -> None: ...
def __copy__(self) -> Tree: ...
@final
class TreeCursor:
@property
def node(self) -> Node | None: ...
@property
def field_id(self) -> int | None: ...
@property
def field_name(self) -> str | None: ...
@property
def depth(self) -> int: ...
@property
def descendant_index(self) -> int: ...
def copy(self) -> TreeCursor: ...
def reset(self, node: Node, /) -> None: ...
def reset_to(self, cursor: TreeCursor, /) -> None: ...
def goto_first_child(self) -> bool: ...
def goto_last_child(self) -> bool: ...
def goto_parent(self) -> bool: ...
def goto_next_sibling(self) -> bool: ...
def goto_previous_sibling(self) -> bool: ...
def goto_descendant(self, index: int, /) -> None: ...
def goto_first_child_for_byte(self, byte: int, /) -> int | None: ...
def goto_first_child_for_point(self, point: Point | tuple[int, int], /) -> int | None: ...
def __copy__(self) -> TreeCursor: ...
@final
class Parser:
@overload
def __init__(
self,
language: Language | None = None,
*,
included_ranges: Sequence[Range] | None = None,
logger: Callable[[LogType, str], None] | None = None,
) -> None: ...
@deprecated("timeout_micros is deprecated")
@overload
def __init__(
self,
language: Language | None = None,
*,
included_ranges: Sequence[Range] | None = None,
timeout_micros: int | None = None,
logger: Callable[[LogType, str], None] | None = None,
) -> None: ...
@property
def language(self) -> Language | None: ...
@language.setter
def language(self, language: Language) -> None: ...
@language.deleter
def language(self) -> None: ...
@property
def included_ranges(self) -> list[Range]: ...
@included_ranges.setter
def included_ranges(self, ranges: Sequence[Range]) -> None: ...
@included_ranges.deleter
def included_ranges(self) -> None: ...
@deprecated("Use the progress_callback in parse()")
@property
def timeout_micros(self) -> int: ...
@deprecated("Use the progress_callback in parse()")
@timeout_micros.setter
def timeout_micros(self, timeout: int) -> None: ...
@deprecated("Use the progress_callback in parse()")
@timeout_micros.deleter
def timeout_micros(self) -> None: ...
@property
def logger(self) -> Callable[[LogType, str], None] | None: ...
@logger.setter
def logger(self, logger: Callable[[LogType, str], None]) -> None: ...
@logger.deleter
def logger(self) -> None: ...
@overload
def parse(
self,
source: ByteString,
/,
old_tree: Tree | None = None,
encoding: Literal["utf8", "utf16", "utf16le", "utf16be"] = "utf8",
) -> Tree: ...
@overload
def parse(
self,
read_callback: Callable[[int, Point], ByteString | None],
/,
old_tree: Tree | None = None,
encoding: Literal["utf8", "utf16", "utf16le", "utf16be"] = "utf8",
progress_callback: Callable[[int, bool], bool] | None = None,
) -> Tree: ...
def reset(self) -> None: ...
def print_dot_graphs(self, file: _SupportsFileno | None, /) -> None: ...
class QueryError(ValueError): ...
class QueryPredicate(Protocol):
def __call__(
self,
predicate: str,
args: list[tuple[str, Literal["capture", "string"]]],
pattern_index: int,
captures: dict[str, list[Node]],
) -> bool: ...
@final
class Query:
def __new__(cls, language: Language, source: str, /) -> Self: ...
def pattern_count(self) -> int: ...
def capture_count(self) -> int: ...
def string_count(self) -> int: ...
def start_byte_for_pattern(self, index: int, /) -> int: ...
def end_byte_for_pattern(self, index: int, /) -> int: ...
def is_pattern_rooted(self, index: int, /) -> bool: ...
def is_pattern_non_local(self, index: int, /) -> bool: ...
def is_pattern_guaranteed_at_step(self, index: int, /) -> bool: ...
def capture_name(self, index: int, /) -> str: ...
def capture_quantifier(
self,
pattern_index: int,
capture_index: int,
/
) -> Literal["", "?", "*", "+"]: ...
def string_value(self, index: int, /) -> str: ...
def disable_capture(self, name: str, /) -> None: ...
def disable_pattern(self, index: int, /) -> None: ...
def pattern_settings(self, index: int, /) -> dict[str, str | None]: ...
def pattern_assertions(self, index: int, /) -> dict[str, tuple[str | None, bool]]: ...
@final
class QueryCursor:
@overload
def __init__(self, query: Query, *, match_limit: int = 0xFFFFFFFF) -> None: ...
@deprecated("timeout_micros is deprecated")
@overload
def __init__(
self,
query: Query,
*,
match_limit: int = 0xFFFFFFFF,
timeout_micros: int = 0
) -> None: ...
@property
def match_limit(self) -> int: ...
@match_limit.setter
def match_limit(self, limit: int) -> None: ...
@match_limit.deleter
def match_limit(self) -> None: ...
@deprecated("Use the progress_callback in matches() or captures()")
@property
def timeout_micros(self) -> int: ...
@deprecated("Use the progress_callback in matches() or captures()")
@timeout_micros.setter
def timeout_micros(self, timeout: int) -> None: ...
@property
def did_exceed_match_limit(self) -> bool: ...
def set_max_start_depth(self, depth: int, /) -> None: ...
def set_byte_range(self, start: int, end: int, /) -> None: ...
def set_point_range(
self,
start: Point | tuple[int, int],
end: Point | tuple[int, int],
/,
) -> None: ...
def captures(
self,
node: Node,
predicate: QueryPredicate | None = None,
progress_callback: Callable[[int], bool] | None = None,
/,
) -> dict[str, list[Node]]: ...
def matches(
self,
node: Node,
predicate: QueryPredicate | None = None,
progress_callback: Callable[[int], bool] | None = None,
/,
) -> list[tuple[int, dict[str, list[Node]]]]: ...
@final
class LookaheadIterator(Iterator[tuple[int, str]]):
@property
def language(self) -> Language: ...
@property
def current_symbol(self) -> int: ...
@property
def current_symbol_name(self) -> str: ...
def reset(self, state: int, /, language: Language | None = None) -> bool: ...
def names(self) -> list[str]: ...
def symbols(self) -> list[int]: ...
def __next__(self) -> tuple[int, str]: ...
@final
class Range:
def __init__(
self,
start_point: Point | tuple[int, int],
end_point: Point | tuple[int, int],
start_byte: int,
end_byte: int,
) -> None: ...
@property
def start_point(self) -> Point: ...
@property
def end_point(self) -> Point: ...
@property
def start_byte(self) -> int: ...
@property
def end_byte(self) -> int: ...
def __eq__(self, other: Any, /) -> bool: ...
def __ne__(self, other: Any, /) -> bool: ...
def __repr__(self) -> str: ...
def __hash__(self) -> int: ...
LANGUAGE_VERSION: Final[int]
MIN_COMPATIBLE_LANGUAGE_VERSION: Final[int]

View File

@@ -0,0 +1,8 @@
{
"name": "Tree-sitter",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"pre_launch": true,
"requests": {}
}

View File

@@ -0,0 +1,63 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .tree_sitter import Parser, get_parser
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def set_ast(self, file):
if not hasattr(file, "tree_sitter"):
parser = get_parser( file.ftype )
if not parser: return
file.tree_sitter = parser
buffer = file.buffer
start_itr, \
end_itr = buffer.get_bounds()
text = buffer.get_text(start_itr, end_itr, True)
tree = file.tree_sitter.parse( text.encode("UTF-8") )
file.ast = tree
def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.FocusedViewEvent):
self.view = event.view
event = Event_Factory.create_event(
"get_file", buffer = self.view.get_buffer()
)
self.emit_to("files", event)
file = event.response
if not file: return
if file.ftype == "buffer": return
self.set_ast(file)
elif isinstance(event, Code_Event_Types.TextChangedEvent):
self.set_ast(event.file)
# root = tree.root_node
# print("Root type:", root.type)
# for child in root.children:
# print(child.type, child.start_point, child.end_point)
def load(self):
...
def unload(self):
...
def run(self):
...

View File

@@ -0,0 +1,57 @@
# Python imports
import os
import ctypes
# Lib imports
# Application imports
from .libs.tree_sitter import Language, Parser
_LIB_PATH = os.path.join(
os.path.dirname(__file__), "languages", "languages.so"
)
_LANGUAGE_LIB = ctypes.CDLL(_LIB_PATH)
def load_language(name: str) -> Language | None:
symbol = f"tree_sitter_{name}"
try:
func = getattr(_LANGUAGE_LIB, symbol)
except AttributeError:
logger.warning(f"Tree-sitter: {name} not found in 'languages.so' shared library...")
return None
func.restype = ctypes.c_void_p
return Language(func())
LANGUAGES = {
"python": load_language("python"),
"python3": load_language("python"),
"javascript": load_language("javascript"),
"html": load_language("html"),
"css": load_language("css"),
"json": load_language("json"),
"java": load_language("java"),
"c": load_language("c"),
"cpp": load_language("cpp"),
"go": load_language("go")
}
def get_parser(lang_name: str) -> Parser | None:
if not lang_name in LANGUAGES: return
language = LANGUAGES[lang_name]
if not language: return
parser = Parser()
parser.language = language
return parser

View File

@@ -0,0 +1,3 @@
"""
Pligin Module
"""

View File

@@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@@ -0,0 +1,52 @@
{
"info": "https://github.com/godotengine/godot/blob/4a280218fcfdd69408cceb74577c9e69086be23a/editor/settings/editor_settings.cpp#L1132",
"command": "lsp-ws-proxy -- godot",
"alt-command": "godot",
"alt-command2": "lsp-ws-proxy --listen 4114 -- godot --headless",
"alt-command3": "godot --headless --lsp-port 7766",
"socket": "ws://127.0.0.1:9999/gdscript",
"socket-two": "ws://127.0.0.1:9999/?name=gdscript",
"initialization-options": {
"processId": null,
"clientInfo": {
"name": "Godot",
"version": "4.4"
},
"rootUri": "file://{workspace.folder}",
"capabilities": {
"workspace": {
"applyEdit": true,
"workspaceEdit": {
"documentChanges": true
}
},
"textDocument": {
"synchronization": {
"dynamicRegistration": true,
"willSave": false,
"didSave": true,
"willSaveWaitUntil": false
},
"completion": {
"dynamicRegistration": true,
"completionItem": {
"snippetSupport": true
}
},
"hover": {
"dynamicRegistration": true
},
"definition": {
"dynamicRegistration": true
},
"references": {
"dynamicRegistration": true
},
"documentSymbol": {
"dynamicRegistration": true
}
}
},
"trace": "off"
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "Godot LSP Client",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"autoload": false,
"requests": {}
}

View File

@@ -0,0 +1,44 @@
# Python imports
from os import path
# Lib imports
import gi
from gi.repository import GLib
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .response_handler import GDScriptHandler
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
dirPth = path.dirname( path.realpath(__file__) )
with open(f"{dirPth}/config/lsp-server-config.json", "r") as f:
config = f.read()
event = Event_Factory.create_event("register_lsp_client",
lang_id = "gdscript",
lang_config = config,
handler = GDScriptHandler
)
self.emit_to("lsp_manager", event)
def unload(self):
event = Event_Factory.create_event("unregister_lsp_client",
lang_id = "gdscript"
)
self.emit_to("lsp_manager", event)
def run(self):
...

View File

@@ -0,0 +1 @@
from .gdscript import GDScriptHandler

View File

@@ -0,0 +1,12 @@
# Python imports
# Lib imports
# Application imports
from lsp_manager.response_handlers.default import DefaultHandler
class GDScriptHandler(DefaultHandler):
"""Uses default handling, can override if Godot needs special logic."""
...

View File

@@ -0,0 +1,3 @@
"""
Pligin Module
"""

View File

@@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@@ -0,0 +1,214 @@
{
"info": "https://download.eclipse.org/jdtls/",
"info-init-options": "https://github.com/eclipse-jdtls/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line",
"info-import-build": "https://www.javahotchocolate.com/tutorials/build-path.html",
"info-external-class-paths": "https://github.com/eclipse-jdtls/eclipse.jdt.ls/issues/3291",
"link": "https://download.eclipse.org/jdtls/milestones/?d",
"command": "lsp-ws-proxy --listen 4114 -- jdtls",
"alt-command": "lsp-ws-proxy -- jdtls",
"alt-command2": "java-language-server",
"socket": "ws://127.0.0.1:9999/java",
"socket-two": "ws://127.0.0.1:9999/?name=jdtls",
"alt-socket": "ws://127.0.0.1:9999/?name=java-language-server",
"initialization-options": {
"bundles": [
"intellicode-core.jar"
],
"workspaceFolders": [
"file://{workspace.folder}"
],
"extendedClientCapabilities": {
"classFileContentsSupport": true,
"executeClientCommandSupport": false
},
"settings": {
"java": {
"autobuild": {
"enabled": true
},
"jdt": {
"ls": {
"javac": {
"enabled": true
},
"java": {
"home": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2"
},
"lombokSupport": {
"enabled": true
},
"protobufSupport":{
"enabled": true
},
"androidSupport": {
"enabled": true
}
}
},
"configuration": {
"updateBuildConfiguration": "automatic",
"maven": {
"userSettings": "{user.home}/.config/jdtls/settings.xml",
"globalSettings": "{user.home}/.config/jdtls/settings.xml"
},
"runtimes": [
{
"name": "JavaSE-17",
"path": "/usr/lib/jvm/java-17-openjdk",
"javadoc": "https://docs.oracle.com/en/java/javase/17/docs/api/",
"default": false
},
{
"name": "JavaSE-22",
"path": "{user.home}/Portable_Apps/sdks/javasdk/jdk-22.0.2",
"javadoc": "https://docs.oracle.com/en/java/javase/22/docs/api/",
"default": true
}
]
},
"classPath": [
"{user.home}/.config/jdtls/m2/repository/**/*-sources.jar",
"lib/**/*-sources.jar"
],
"docPath": [
"{user.home}/.config/jdtls/m2/repository/**/*-javadoc.jar",
"lib/**/*-javadoc.jar"
],
"project": {
"encoding": "ignore",
"outputPath": "bin",
"referencedLibraries": [
"{user.home}/.config/jdtls/m2/repository/**/*.jar",
"lib/**/*.jar"
],
"importOnFirstTimeStartup": "automatic",
"importHint": true,
"resourceFilters": [
"node_modules",
"\\.git"
],
"sourcePaths": [
"src",
"{user.home}/.config/jdtls/m2/repository/**/*.jar"
]
},
"sources": {
"organizeImports": {
"starThreshold": 99,
"staticStarThreshold": 99
}
},
"imports": {
"gradle": {
"wrapper": {
"checksums": []
}
}
},
"import": {
"maven": {
"enabled": true,
"offline": {
"enabled": false
},
"disableTestClasspathFlag": false
},
"gradle": {
"enabled": false,
"wrapper": {
"enabled": true
},
"version": "",
"home": "abs(static/gradle-7.3.3)",
"java": {
"home": "abs(static/launch_jres/17.0.6-linux-x86_64)"
},
"offline": {
"enabled": false
},
"arguments": [],
"jvmArguments": [],
"user": {
"home": ""
},
"annotationProcessing": {
"enabled": true
}
},
"exclusions": [
"**/node_modules/**",
"**/.metadata/**",
"**/archetype-resources/**",
"**/META-INF/maven/**"
],
"generatesMetadataFilesAtProjectRoot": false
},
"maven": {
"downloadSources": true,
"updateSnapshots": true
},
"silentNotification": true,
"contentProvider": {
"preferred": "fernflower"
},
"signatureHelp": {
"enabled": true,
"description": {
"enabled": true
}
},
"completion": {
"enabled": true,
"engine": "ecj",
"matchCase": "firstletter",
"maxResults": 25,
"guessMethodArguments": true,
"lazyResolveTextEdit": {
"enabled": true
},
"postfix": {
"enabled": true
},
"favoriteStaticMembers": [
"org.junit.Assert.*",
"org.junit.Assume.*",
"org.junit.jupiter.api.Assertions.*",
"org.junit.jupiter.api.Assumptions.*",
"org.junit.jupiter.api.DynamicContainer.*",
"org.junit.jupiter.api.DynamicTest.*"
],
"importOrder": [
"#",
"java",
"javax",
"org",
"com"
]
},
"references": {
"includeAccessors": true,
"includeDecompiledSources": true
},
"codeGeneration": {
"toString": {
"template": "${object.className}{${member.name()}=${member.value}, ${otherMembers}}"
},
"insertionLocation": "afterCursor",
"useBlocks": true
},
"implementationsCodeLens": {
"enabled": true
},
"referencesCodeLens": {
"enabled": true
},
"progressReports": {
"enabled": false
},
"saveActions": {
"organizeImports": true
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
{
"name": "Java LSP Client",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"autoload": false,
"requests": {}
}

View File

@@ -0,0 +1,43 @@
# Python imports
from os import path
# Lib imports
import gi
from gi.repository import GLib
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .response_handler import JavaHandler
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
dirPth = path.dirname( path.realpath(__file__) )
with open(f"{dirPth}/config/lsp-server-config.json", "r") as f:
config = f.read()
event = Event_Factory.create_event("register_lsp_client",
lang_id = "java",
lang_config = config,
handler = JavaHandler
)
self.emit_to("lsp_manager", event)
def unload(self):
event = Event_Factory.create_event("unregister_lsp_client",
lang_id = "java"
)
self.emit_to("lsp_manager", event)
def run(self):
...

View File

@@ -0,0 +1 @@
from .java import JavaHandler

View File

@@ -0,0 +1,51 @@
# Python imports
# Lib imports
import gi
gi.require_version('GtkSource', '4')
from gi.repository import GtkSource
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from lsp_manager.response_handlers.default import DefaultHandler
class JavaHandler(DefaultHandler):
"""Java-specific: overrides definition, handles classFileContents."""
def handle(self, method: str, response, controller):
match method:
case "textDocument/definition":
self._handle_definition(response, controller)
case "java/classFileContents":
self._handle_class_file_contents(response)
case _:
super().handle(method, response, controller)
def _handle_definition(self, response, controller):
if not response: return
uri = response[0]["uri"]
if "jdt://" in uri:
controller._lsp_java_class_file_contents(uri)
return
self._prompt_goto_request(uri, response[0]["range"])
def _handle_class_file_contents(self, text: str):
event = Event_Factory.create_event("get_active_view")
self.emit_to("source_views", event)
view = event.response
file = view.command.exec("new_file")
buffer = view.get_buffer()
itr = buffer.get_iter_at_mark(buffer.get_insert())
lm = GtkSource.LanguageManager.get_default()
language = lm.get_language("java")
file.ftype = "java"
buffer.set_language(language)
buffer.insert(itr, text, -1)

View File

@@ -0,0 +1,3 @@
"""
Pligin Module
"""

View File

@@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@@ -0,0 +1,3 @@
"""
LSP Clients Module
"""

View File

@@ -0,0 +1,56 @@
# Python imports
# Lib imports
# Application imports
from ..config import get_lsp_init_config
from ..dto.code.lsp.lsp_messages import get_message_str
from ..dto.code.lsp.lsp_message_structs import \
LSPResponseTypes, ClientRequest, ClientNotification
from .lsp_client_websocket import LSPClientWebsocket
class LSPClient(LSPClientWebsocket):
def __init__(self):
super(LSPClient, self).__init__()
self._socket: str = ""
self._language: str = ""
self._workspace_path: str = ""
self._message_id: int = -1
self._event_history: dict[int, str] = {}
self._init_params: dict = get_lsp_init_config()
self._init_opts: dict[str, str] = {}
self.doc_vers: dict[str, int] = {}
def set_language(self, language: str):
self._language = language
def set_workspace_path(self, workspace_path: str):
self._workspace_path = workspace_path
def set_init_opts(self, init_opts: dict[str, str]):
self._init_opts = init_opts
def set_socket(self, socket: str):
self._socket = socket
def unset_socket(self):
self._socket = ""
def send_notification(self, method: str, params: dict = {}):
self._send_message( ClientNotification(method, params) )
def send_request(self, method: str, params: dict = {}):
self._message_id += 1
self._event_history[self._message_id] = method
self._send_message( ClientRequest(self._message_id, method, params) )
def get_event_by_id(self, message_id: int) -> str:
if not message_id in self._event_history: return
return self._event_history[message_id]
def handle_lsp_response(self, lsp_response: LSPResponseTypes | dict):
raise NotImplementedError

View File

@@ -3,12 +3,13 @@
# Lib imports
# Application imports
from .lsp_controller_events import LSPControllerEvents
from libs.dto.code.lsp.lsp_message_structs import ClientRequest, ClientNotification
from ..dto.code.lsp.lsp_message_structs import ClientRequest, ClientNotification
from .lsp_client_events import LSPClientEvents
class LSPControllerBase(LSPControllerEvents):
class LSPClientBase(LSPClientEvents):
def _send_message(self, data: ClientRequest or ClientNotification):
raise NotImplementedError

View File

@@ -0,0 +1,160 @@
# Python imports
import os
# Lib imports
# Application imports
from ..dto.code.lsp.lsp_messages import get_message_obj
from ..dto.code.lsp.lsp_messages import didopen_notification
from ..dto.code.lsp.lsp_messages import didsave_notification
from ..dto.code.lsp.lsp_messages import didclose_notification
from ..dto.code.lsp.lsp_messages import didchange_notification
from ..dto.code.lsp.lsp_messages import didchange_notification_range
from ..dto.code.lsp.lsp_messages import completion_request
from ..dto.code.lsp.lsp_messages import definition_request
from ..dto.code.lsp.lsp_messages import implementation_request
from ..dto.code.lsp.lsp_messages import references_request
from ..dto.code.lsp.lsp_messages import symbols_request
from ..dto.code.lsp.lsp_messages import shutdown_request
from ..dto.code.lsp.lsp_messages import exit_request
class LSPClientEvents:
def send_initialize_message(self):
folder_name = os.path.basename(self._workspace_path)
workspace_uri = f"file://{self._workspace_path}"
self._init_params["processId"] = None
self._init_params["rootPath"] = self._workspace_path
self._init_params["rootUri"] = workspace_uri
self._init_params["workspaceFolders"] = [
{
"name": folder_name,
"uri": workspace_uri
}
]
self._init_params["initializationOptions"] = self._init_opts
self.send_request("initialize", self._init_params)
def send_initialized_notification(self):
self.send_notification("initialized")
def send_shutdown_request(self):
self.send_request("shutdown")
def send_exit_notification(self):
self.send_notification("exit")
def _lsp_did_open(self, data: dict):
method = "textDocument/didOpen"
params = didopen_notification["params"]
self.doc_vers[ data["uri"] ] = -1
params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["text"] = data["text"]
self.send_notification( method, params )
def _lsp_did_save(self, data: dict):
method = "textDocument/didSave"
params = didsave_notification["params"]
params["textDocument"]["uri"] = data["uri"]
params["text"] = data["text"]
self.send_notification( method, params )
def _lsp_did_close(self, data: dict):
method = "textDocument/didClose"
params = didclose_notification["params"]
params["textDocument"]["uri"] = data["uri"]
self.send_notification( method, params )
def _lsp_did_change(self, data: dict):
method = "textDocument/didChange"
params = didchange_notification["params"]
params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["version"] = data["version"]
contentChanges = params["contentChanges"][0]
contentChanges["text"] = data["text"]
self.send_notification( method, params )
def _lsp_did_change_range(self, data: dict):
method = "textDocument/didChange"
params = didchange_notification_range["params"]
params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["version"] = data["version"]
contentChanges = params["contentChanges"][0]
start = contentChanges["range"]["start"]
end = contentChanges["range"]["end"]
contentChanges["text"] = data["text"]
start["line"] = data["line"]
start["character"] = data["column"]
end["line"] = data["end_line"]
end["character"] = data["end_column"]
self.send_notification( method, params )
def _lsp_definition(self, data: dict):
method = "textDocument/definition"
params = definition_request["params"]
params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["version"] = data["version"]
params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"]
self.send_request( method, params )
def _lsp_implementation(self, data: dict):
method = "textDocument/implementation"
params = implementation_request["params"]
params["textDocument"]["uri"] = data["uri"]
params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"]
self.send_request( method, params )
def _lsp_references(self, data: dict):
method = "textDocument/references"
params = references_request["params"]
params["textDocument"]["uri"] = data["uri"]
params["textDocument"]["languageId"] = data["language_id"]
params["textDocument"]["version"] = data["version"]
params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"]
self.send_request( method, params )
def _lsp_completion(self, data: dict):
method = "textDocument/completion"
params = completion_request["params"]
params["textDocument"]["uri"] = data["uri"]
params["position"]["line"] = data["line"]
params["position"]["character"] = data["column"]
self.send_request( method, params )
def _lsp_java_class_file_contents(self, uri: str):
method = "java/classFileContents"
params = {
"uri": uri
}
self.send_request( method, params )

View File

@@ -0,0 +1,57 @@
# Python imports
# Lib imports
from gi.repository import GLib
# Application imports
# from libs import websockets
from ..dto.code.lsp.lsp_messages import get_message_str, get_message_obj
from ..dto.code.lsp.lsp_message_structs import \
LSPResponseTypes, ClientRequest, ClientNotification, \
LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
from .lsp_client_base import LSPClientBase
from .websocket import Websocket
class LSPClientWebsocket(LSPClientBase):
def _send_message(self, data: ClientRequest | ClientNotification):
if not data: return
message_str = get_message_str(data)
message_size = len(message_str)
message = f"Content-Length: {message_size}\r\n\r\n{message_str}"
logger.debug(f"Client: {message_str}")
self.websocket.send(message_str)
def start_client(self):
self.websocket = Websocket()
self.websocket.set_socket(self._socket)
self.websocket.set_callback(self._monitor_lsp_response)
self.websocket.start_client()
return self.websocket
def stop_client(self):
if not hasattr(self, "websocket"): return
self.websocket.close_client()
def _monitor_lsp_response(self, data: dict | None):
if not data: return {}
message = get_message_obj(data)
keys = message.keys()
lsp_response = data
if "result" in keys:
lsp_response = LSPResponseRequest(**get_message_obj(data))
if "method" in keys:
lsp_response = LSPResponseNotification(**get_message_obj(data)) if not "id" in keys else LSPIDResponseNotification( **get_message_obj(data) )
if isinstance(lsp_response, str):
lsp_response = get_message_obj(lsp_response)
GLib.idle_add(self.handle_lsp_response, lsp_response)

View File

@@ -9,7 +9,7 @@ from ..libs import websocket
class WebsocketClient:
class Websocket:
def __init__(self):
self.ws = None
self._socket = None
@@ -59,4 +59,4 @@ class WebsocketClient:
on_error = self.on_error,
on_close = self.on_close)
self.ws.run_forever(reconnect = 0.5)
self.ws.run_forever(reconnect = 0.5)

View File

@@ -0,0 +1,60 @@
# Python imports
from concurrent.futures import ThreadPoolExecutor
# Lib imports
# Application imports
from .config import get_lsp_connect_timout
from .mixins.client_manager_events_mixin import ClientManagerEventsMixin
from .client.lsp_client import LSPClient
class ClientManager(ClientManagerEventsMixin):
def __init__(self):
super(ClientManager, self).__init__()
self._cache_refresh_timeout_id: int = None
self.executor: ThreadPoolExecutor = ThreadPoolExecutor(max_workers = 1)
self.active_language_id: str = ""
self.clients: dict = {}
def create_client(
self,
lang_id: str,
workspace_path: str,
init_opts: dict[str, str],
address: str = "127.0.0.1",
port: str = "9999"
) -> LSPClient:
if lang_id in self.clients: return None
uri = f"ws://{address}:{port}/{lang_id}?workspace={workspace_path}"
client = LSPClient()
client.set_socket(uri)
client.set_language(lang_id)
client.set_workspace_path(workspace_path)
client.set_init_opts(init_opts)
client.start_client()
if not client.websocket.wait_for_connection(timeout = get_lsp_connect_timout()):
logger.error(f"Failed to connect to LSP server for {lang_id}")
return None
self.clients[lang_id] = client
return client
def close_client(self, lang_id: str) -> bool:
if lang_id not in self.clients: return False
controller = self.clients.pop(lang_id)
controller.stop_client()
return True
def get_active_client(self) -> LSPClient:
return self.clients[self.active_language_id]

View File

@@ -0,0 +1,87 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import Gtk
from gi.repository import GtkSource
# Application imports
class Commands:
lsp_manager: callable = None
class lsp_manager_toggle:
@staticmethod
def execute(
source_view: GtkSource,
char_str: str,
modkeys_states: tuple
):
logger.debug("Command: LSP Manager Toggle")
if Commands.lsp_manager.ui_manager.is_visible():
Commands.lsp_manager.ui_manager.hide()
else:
Commands.lsp_manager.ui_manager.show()
class lsp_references:
@staticmethod
def execute(
view: GtkSource,
char_str: str,
modkeys_states: tuple
):
logger.debug("Command: LSP References")
file = view.command.exec("get_current_file")
buffer = view.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
column = iter.get_line_offset()
Commands.lsp_manager.client_manager.process_references_definition(
file.ftype, file.fpath, line, column
)
class lsp_implementation:
@staticmethod
def execute(
view: GtkSource,
char_str: str,
modkeys_states: tuple
):
logger.debug("Command: LSP Implements")
file = view.command.exec("get_current_file")
buffer = view.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
column = iter.get_line_offset()
Commands.lsp_manager.client_manager.process_implementation_definition(
file.ftype, file.fpath, line, column
)
class lsp_definition:
@staticmethod
def execute(
view: GtkSource,
char_str: str,
modkeys_states: tuple
):
logger.debug("Command: LSP Definition (Go-To)")
file = view.command.exec("get_current_file")
buffer = view.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
column = iter.get_line_offset()
Commands.lsp_manager.client_manager.process_definition(
file.ftype, file.fpath, line, column
)

View File

@@ -0,0 +1,38 @@
# Python imports
from os import path
import json
# Lib imports
# Application imports
LSP_HOST: str = "127.0.0.1"
LSP_PORT: int = 9999
LSP_CONNECT_TIMOUT: float = 5.0
def get_lsp_host_addr() -> str:
return LSP_HOST
def get_lsp_host_port() -> int:
return LSP_PORT
def get_lsp_connect_timout() -> float:
return LSP_CONNECT_TIMOUT
def get_lsp_init_config() -> dict:
try:
_USER_HOME = path.expanduser('~')
_SCRIPT_PTH = path.dirname( path.realpath(__file__) )
_LSP_INIT_CONFIG = f"{_SCRIPT_PTH}/configs/initialize-params-slim.json"
with open(_LSP_INIT_CONFIG) as file:
data = file.read()
return json.loads(data)
except Exception as e:
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
return {}

View File

@@ -0,0 +1,8 @@
"""
Libs Code DTO(s) Events Package
"""
from .lsp_event import LspEvent
from .register_lsp_client_event import RegisterLspClientEvent
from .unregister_lsp_client_event import UnregisterLspClientEvent

View File

@@ -0,0 +1,13 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
# Application imports
from libs.dto.code.events import CodeEvent
@dataclass
class LspEvent(CodeEvent):
...

View File

@@ -0,0 +1,17 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
# Application imports
from ....response_handlers.base_handler import BaseHandler
from .lsp_event import LspEvent
@dataclass
class RegisterLspClientEvent(LspEvent):
lang_id: str = ""
lang_config: str = "{}"
handler: BaseHandler = None

View File

@@ -0,0 +1,13 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
# Application imports
from .lsp_event import LspEvent
@dataclass
class UnregisterLspClientEvent(LspEvent):
lang_id: str = ""

View File

@@ -96,10 +96,10 @@ didchange_notification_range = {
"uri": "file://",
"languageId": "python",
"version": 1,
"text": ""
},
"contentChanges": [
{
"text": "",
"range": {
"start": {
"line": 1,
@@ -108,9 +108,8 @@ didchange_notification_range = {
"end": {
"line": 1,
"character": 1,
},
"rangeLength": 0
}
}
},
}
]
}
@@ -125,19 +124,11 @@ completion_request = {
"method": "textDocument/completion",
"params": {
"textDocument": {
"uri": "file://",
"languageId": "python",
"version": 1,
"text": ""
"uri": "file://"
},
"position": {
"line": 5,
"character": 12,
"offset": 0
},
"contet": {
"triggerKind": 3,
"triggerCharacter": ""
"character": 12
}
}
}
@@ -159,6 +150,19 @@ definition_request = {
}
}
implementation_request = {
"method": "textDocument/implementation",
"params": {
"textDocument": {
"uri": "file://"
},
"position": {
"line": 5,
"character": 12
}
}
}
references_request = {
"method": "textDocument/references",
"params": {
@@ -179,7 +183,6 @@ references_request = {
}
}
symbols_request = {
"method": "textDocument/documentSymbol",
"params": {
@@ -190,4 +193,15 @@ symbols_request = {
"text": ""
}
}
}
}
shutdown_request = {
"method": "shutdown",
"params": None
}
exit_request = {
"method": "exit",
"params": None
}

Some files were not shown because too many files have changed in this diff Show More