Compare commits
22 Commits
77a3b71d31
...
release/ne
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c3de1334a | |||
| 41f3501e1f | |||
| 12b5fe7304 | |||
| d8e0185d1c | |||
| fd1f5b8d64 | |||
| 69e766dc28 | |||
| 5911f37449 | |||
| 6e46279da4 | |||
| d90415bffc | |||
| 62a866d9bb | |||
| dc2997ec16 | |||
| 70877a7ee1 | |||
| cb73f6b3b0 | |||
| e6eaa1d83c | |||
| 62731ae766 | |||
| b5cec0d049 | |||
| c821f30880 | |||
| 13908d7ba7 | |||
| ec69d4db73 | |||
| 511138316a | |||
| d6e0823e21 | |||
| 54e7b58c24 |
32
README.md
32
README.md
@@ -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
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
19
TODO.md
19
TODO.md
@@ -1,22 +1,15 @@
|
||||
___
|
||||
### Add
|
||||
1. Add Godot LSP Client
|
||||
1. Add Collapsable code blocks
|
||||
1. Add Plugin to <Shift\><Ctrl\>| and <Ctrl\>| to split views up, down, left, right
|
||||
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
|
||||
1. Make **lsp_manager** hard coded values configurable, plus add fields to UI
|
||||
___
|
||||
### Fix
|
||||
- Fix **telescope** search selection when items hidden such that
|
||||
hidden items don't get selected on up/down key
|
||||
- Fix to make acive tab on **tabs_bar** scroll to center
|
||||
- Fix **file_state_watcher** to prompt refrsh if external changes applied
|
||||
- Fix on lsp client unload to close files lsp side and unload server endpoint
|
||||
- Fix multi-select <Shift\><Ctrl\> left/right block select movement de-sync
|
||||
from leader when '_' in word
|
||||
|
||||
___
|
||||
### 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
BIN
images/pic1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
BIN
images/pic2.png
Normal file
BIN
images/pic2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
images/pic3.png
Normal file
BIN
images/pic3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
images/pic4.png
Normal file
BIN
images/pic4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
images/pic5.png
Normal file
BIN
images/pic5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 951 KiB |
BIN
images/pic6.png
Normal file
BIN
images/pic6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 901 KiB |
@@ -14,53 +14,94 @@ class Commenter(CodeCommentTagsMixin):
|
||||
|
||||
def keyboard_tggl_comment(self, buffer):
|
||||
language = buffer.get_language()
|
||||
if language is None: return
|
||||
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
|
||||
|
||||
start_tag += " "
|
||||
end_tag = end_tag or ""
|
||||
bounds = buffer.get_selection_bounds()
|
||||
if bounds:
|
||||
self._bounds_comment(
|
||||
start_tag, end_tag, bounds, buffer
|
||||
|
||||
(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:
|
||||
self._line_comment(start_tag, end_tag, buffer)
|
||||
|
||||
|
||||
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
|
||||
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, 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()
|
||||
def _bounds_comment(self, buffer, start_tag: str, end_tag: str, bounds):
|
||||
def indent_len(s): return len(s) - len(s.lstrip())
|
||||
|
||||
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()
|
||||
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_itr, end_itr)
|
||||
buffer.insert(start_itr, text)
|
||||
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),
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ class Plugin(PluginCode):
|
||||
if len(history) == history_size:
|
||||
history.pop(0)
|
||||
|
||||
history.append(event.file)
|
||||
history.append(event.file.fpath)
|
||||
|
||||
def load(self):
|
||||
self._manage_signals("register_command")
|
||||
@@ -60,6 +60,6 @@ class Handler:
|
||||
|
||||
view._on_uri_data_received(
|
||||
[
|
||||
history.pop().replace("file://", "")
|
||||
f"file://{history.pop()}"
|
||||
]
|
||||
)
|
||||
|
||||
@@ -14,16 +14,24 @@ from .helpers import clear_temp_cut_buffer_delayed, set_temp_cut_buffer_delayed
|
||||
|
||||
class Handler:
|
||||
@staticmethod
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
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()
|
||||
@@ -36,9 +44,14 @@ class Handler:
|
||||
if not hasattr(view, "_cut_buffer"):
|
||||
view._cut_buffer = ""
|
||||
|
||||
line_str = buffer.get_text(start_itr, end_itr, True)
|
||||
view._cut_buffer += line_str
|
||||
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)
|
||||
@@ -23,7 +23,7 @@ class Plugin(PluginCode):
|
||||
def load(self):
|
||||
self._manage_signals("register_command")
|
||||
|
||||
def load(self):
|
||||
def unload(self):
|
||||
self._manage_signals("unregister_command")
|
||||
|
||||
def _manage_signals(self, action: str):
|
||||
|
||||
3
plugins/code/commands/split_pane/__init__.py
Normal file
3
plugins/code/commands/split_pane/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Module
|
||||
"""
|
||||
3
plugins/code/commands/split_pane/__main__.py
Normal file
3
plugins/code/commands/split_pane/__main__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Package
|
||||
"""
|
||||
77
plugins/code/commands/split_pane/close_split_view.py
Normal file
77
plugins/code/commands/split_pane/close_split_view.py
Normal 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()
|
||||
77
plugins/code/commands/split_pane/create_split_view.py
Normal file
77
plugins/code/commands/split_pane/create_split_view.py
Normal 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()
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Toggle Source View",
|
||||
"name": "Split Pane",
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"support": "",
|
||||
100
plugins/code/commands/split_pane/plugin.py
Normal file
100
plugins/code/commands/split_pane/plugin.py
Normal 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):
|
||||
...
|
||||
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
Plugin Module
|
||||
"""
|
||||
@@ -1,3 +0,0 @@
|
||||
"""
|
||||
Plugin Package
|
||||
"""
|
||||
@@ -1,64 +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 unload(self):
|
||||
event = Event_Factory.create_event("unregister_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()
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -17,13 +17,22 @@ 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, self.emit)
|
||||
elif event.file.is_externally_modified():
|
||||
file_is_externally_modified(event, self.emit)
|
||||
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):
|
||||
...
|
||||
|
||||
@@ -10,23 +10,45 @@ from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
|
||||
|
||||
def file_is_deleted(event, emit):
|
||||
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
|
||||
)
|
||||
emit(event)
|
||||
|
||||
|
||||
def file_is_externally_modified(event, emit):
|
||||
# event = Event_Factory.create_event(
|
||||
# "file_externally_modified",
|
||||
# file = event.file,
|
||||
# buffer = event.buffer
|
||||
# )
|
||||
# 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()
|
||||
|
||||
@@ -13,8 +13,6 @@ from gi.repository import Gtk
|
||||
|
||||
|
||||
def add_prettify_json(buffer, menu):
|
||||
menu.append(separator)
|
||||
|
||||
def on_prettify_json(menuitem, buffer):
|
||||
start_itr, \
|
||||
end_itr = buffer.get_start_iter(), buffer.get_end_iter()
|
||||
|
||||
3
plugins/code/event-watchers/tree_sitter/__init__.py
Normal file
3
plugins/code/event-watchers/tree_sitter/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Module
|
||||
"""
|
||||
3
plugins/code/event-watchers/tree_sitter/__main__.py
Normal file
3
plugins/code/event-watchers/tree_sitter/__main__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Package
|
||||
"""
|
||||
134
plugins/code/event-watchers/tree_sitter/languages/scripts/build.sh
Executable file
134
plugins/code/event-watchers/tree_sitter/languages/scripts/build.sh
Executable 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 "$@"
|
||||
3
plugins/code/event-watchers/tree_sitter/libs/__init__.py
Normal file
3
plugins/code/event-watchers/tree_sitter/libs/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Libs Module
|
||||
"""
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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]
|
||||
8
plugins/code/event-watchers/tree_sitter/manifest.json
Normal file
8
plugins/code/event-watchers/tree_sitter/manifest.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Tree-sitter",
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"support": "",
|
||||
"pre_launch": true,
|
||||
"requests": {}
|
||||
}
|
||||
63
plugins/code/event-watchers/tree_sitter/plugin.py
Normal file
63
plugins/code/event-watchers/tree_sitter/plugin.py
Normal 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):
|
||||
...
|
||||
57
plugins/code/event-watchers/tree_sitter/tree_sitter.py
Normal file
57
plugins/code/event-watchers/tree_sitter/tree_sitter.py
Normal 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
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"socket": "ws://127.0.0.1:9999/gdscript",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=gdscript",
|
||||
"initialization-options": {
|
||||
"processId": ,
|
||||
"processId": null,
|
||||
"clientInfo": {
|
||||
"name": "Godot",
|
||||
"version": "4.4"
|
||||
|
||||
@@ -11,7 +11,7 @@ from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
from plugins.plugin_types import PluginCode
|
||||
|
||||
from .response_handler import GodotHandler
|
||||
from .response_handler import GDScriptHandler
|
||||
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ class Plugin(PluginCode):
|
||||
event = Event_Factory.create_event("register_lsp_client",
|
||||
lang_id = "gdscript",
|
||||
lang_config = config,
|
||||
handler = GodotHandler
|
||||
handler = GDScriptHandler
|
||||
)
|
||||
self.emit_to("lsp_manager", event)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .python import PythonHandler
|
||||
from .gdscript import GDScriptHandler
|
||||
@@ -7,6 +7,6 @@ from lsp_manager.response_handlers.default import DefaultHandler
|
||||
|
||||
|
||||
|
||||
class GodotHandler(DefaultHandler):
|
||||
class GDScriptHandler(DefaultHandler):
|
||||
"""Uses default handling, can override if Godot needs special logic."""
|
||||
...
|
||||
@@ -1,13 +1,9 @@
|
||||
# Python imports
|
||||
import threading
|
||||
from os import path
|
||||
import json
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
from gi.repository import GLib
|
||||
|
||||
# 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
|
||||
@@ -19,38 +15,30 @@ class LSPClient(LSPClientWebsocket):
|
||||
def __init__(self):
|
||||
super(LSPClient, self).__init__()
|
||||
|
||||
# https://github.com/microsoft/multilspy/tree/main/src/multilspy/language_servers
|
||||
# initialize-params-slim.json was created off of jedi_language_server one
|
||||
# self._init_params = settings_manager.get_lsp_init_data()
|
||||
|
||||
self._socket: str = ""
|
||||
self._language: str = ""
|
||||
self._init_params: dict = {}
|
||||
self._event_history: dict[int, str] = {}
|
||||
|
||||
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().replace("{user.home}", _USER_HOME)
|
||||
self._init_params = json.loads(data)
|
||||
except Exception as e:
|
||||
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
|
||||
|
||||
self._workspace_path: str = ""
|
||||
self._message_id: int = -1
|
||||
self._socket = None
|
||||
self.read_lock = threading.Lock()
|
||||
self.write_lock = threading.Lock()
|
||||
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 = None
|
||||
self._socket = ""
|
||||
|
||||
def send_notification(self, method: str, params: dict = {}):
|
||||
self._send_message( ClientNotification(method, params) )
|
||||
@@ -64,5 +52,5 @@ class LSPClient(LSPClientWebsocket):
|
||||
if not message_id in self._event_history: return
|
||||
return self._event_history[message_id]
|
||||
|
||||
def handle_lsp_response(self, lsp_response: LSPResponseTypes):
|
||||
def handle_lsp_response(self, lsp_response: LSPResponseTypes | dict):
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -9,19 +9,24 @@ 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, init_ops: dict, workspace_file: str, workspace_uri: str):
|
||||
folder_name = os.path.basename(workspace_file)
|
||||
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"] = workspace_file
|
||||
self._init_params["rootPath"] = self._workspace_path
|
||||
self._init_params["rootUri"] = workspace_uri
|
||||
self._init_params["workspaceFolders"] = [
|
||||
{
|
||||
@@ -30,15 +35,22 @@ class LSPClientEvents:
|
||||
}
|
||||
]
|
||||
|
||||
self._init_params["initializationOptions"] = init_ops
|
||||
self._init_params["initializationOptions"] = self._init_opts
|
||||
self.send_request("initialize", self._init_params)
|
||||
|
||||
def send_initialized_message(self):
|
||||
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"]
|
||||
@@ -76,24 +88,24 @@ class LSPClientEvents:
|
||||
|
||||
self.send_notification( method, params )
|
||||
|
||||
# def _lsp_did_change(self, data: dict):
|
||||
# method = "textDocument/didChange"
|
||||
# params = didchange_notification_range["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"]
|
||||
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"] = 0
|
||||
# end["line"] = data["line"]
|
||||
# end["character"] = data["column"]
|
||||
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 )
|
||||
self.send_notification( method, params )
|
||||
|
||||
def _lsp_definition(self, data: dict):
|
||||
method = "textDocument/definition"
|
||||
@@ -107,13 +119,33 @@ class LSPClientEvents:
|
||||
|
||||
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["textDocument"]["languageId"] = data["language_id"]
|
||||
params["textDocument"]["version"] = data["version"]
|
||||
params["position"]["line"] = data["line"]
|
||||
params["position"]["character"] = data["column"]
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from ..dto.code.lsp.lsp_message_structs import \
|
||||
LSPResponseRequest, LSPResponseNotification, LSPIDResponseNotification
|
||||
|
||||
from .lsp_client_base import LSPClientBase
|
||||
from .websocket_client import WebsocketClient
|
||||
from .websocket import Websocket
|
||||
|
||||
|
||||
|
||||
@@ -24,26 +24,26 @@ class LSPClientWebsocket(LSPClientBase):
|
||||
message = f"Content-Length: {message_size}\r\n\r\n{message_str}"
|
||||
|
||||
logger.debug(f"Client: {message_str}")
|
||||
self.ws_client.send(message_str)
|
||||
self.websocket.send(message_str)
|
||||
|
||||
def start_client(self):
|
||||
self.ws_client = WebsocketClient()
|
||||
self.ws_client.set_socket(self._socket)
|
||||
self.ws_client.set_callback(self._monitor_lsp_response)
|
||||
self.ws_client.start_client()
|
||||
self.websocket = Websocket()
|
||||
self.websocket.set_socket(self._socket)
|
||||
self.websocket.set_callback(self._monitor_lsp_response)
|
||||
self.websocket.start_client()
|
||||
|
||||
return self.ws_client
|
||||
return self.websocket
|
||||
|
||||
def stop_client(self):
|
||||
if not hasattr(self, "ws_client"): return
|
||||
self.ws_client.close_client()
|
||||
if not hasattr(self, "websocket"): return
|
||||
self.websocket.close_client()
|
||||
|
||||
def _monitor_lsp_response(self, data: dict | None):
|
||||
if not data: return
|
||||
if not data: return {}
|
||||
|
||||
message = get_message_obj(data)
|
||||
keys = message.keys()
|
||||
lsp_response = None
|
||||
lsp_response = data
|
||||
|
||||
if "result" in keys:
|
||||
lsp_response = LSPResponseRequest(**get_message_obj(data))
|
||||
@@ -51,6 +51,7 @@ class LSPClientWebsocket(LSPClientBase):
|
||||
if "method" in keys:
|
||||
lsp_response = LSPResponseNotification(**get_message_obj(data)) if not "id" in keys else LSPIDResponseNotification( **get_message_obj(data) )
|
||||
|
||||
if not lsp_response: return
|
||||
if isinstance(lsp_response, str):
|
||||
lsp_response = get_message_obj(lsp_response)
|
||||
|
||||
GLib.idle_add(self.handle_lsp_response, lsp_response)
|
||||
@@ -9,7 +9,7 @@ from ..libs import websocket
|
||||
|
||||
|
||||
|
||||
class WebsocketClient:
|
||||
class Websocket:
|
||||
def __init__(self):
|
||||
self.ws = None
|
||||
self._socket = None
|
||||
@@ -4,14 +4,15 @@ from concurrent.futures import ThreadPoolExecutor
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .mixins.lsp_client_events_mixin import LSPClientEventsMixin
|
||||
from .config import get_lsp_connect_timout
|
||||
from .mixins.client_manager_events_mixin import ClientManagerEventsMixin
|
||||
from .client.lsp_client import LSPClient
|
||||
|
||||
|
||||
|
||||
class LSPManagerClient(LSPClientEventsMixin):
|
||||
class ClientManager(ClientManagerEventsMixin):
|
||||
def __init__(self):
|
||||
super(LSPManagerClient, self).__init__()
|
||||
super(ClientManager, self).__init__()
|
||||
|
||||
self._cache_refresh_timeout_id: int = None
|
||||
|
||||
@@ -22,22 +23,24 @@ class LSPManagerClient(LSPClientEventsMixin):
|
||||
|
||||
def create_client(
|
||||
self,
|
||||
lang_id: str = "python",
|
||||
workspace_uri: str = "",
|
||||
init_opts: dict = {}
|
||||
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
|
||||
|
||||
address = "127.0.0.1"
|
||||
port = 9999
|
||||
uri = f"ws://{address}:{port}/{lang_id}"
|
||||
uri = f"ws://{address}:{port}/{lang_id}?workspace={workspace_path}"
|
||||
client = LSPClient()
|
||||
|
||||
client.set_language(lang_id)
|
||||
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.ws_client.wait_for_connection(timeout = 5.0):
|
||||
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
|
||||
|
||||
87
plugins/code/language_server_clients/lsp_manager/commands.py
Normal file
87
plugins/code/language_server_clients/lsp_manager/commands.py
Normal 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
|
||||
)
|
||||
38
plugins/code/language_server_clients/lsp_manager/config.py
Normal file
38
plugins/code/language_server_clients/lsp_manager/config.py
Normal 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 {}
|
||||
@@ -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": {
|
||||
@@ -191,3 +194,14 @@ symbols_request = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shutdown_request = {
|
||||
"method": "shutdown",
|
||||
"params": None
|
||||
}
|
||||
|
||||
exit_request = {
|
||||
"method": "exit",
|
||||
"params": None
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from libs.controllers.controller_base import ControllerBase
|
||||
@@ -11,10 +14,11 @@ from .dto.code.events import \
|
||||
from .dto.code.lsp.lsp_message_structs import \
|
||||
LSPResponseTypes, LSPResponseRequest, LSPResponseNotification
|
||||
|
||||
from .ui_manager import UIManager
|
||||
|
||||
from .provider import Provider
|
||||
from .provider_response_cache import ProviderResponseCache
|
||||
from .lsp_manager_ui import LSPManagerUI
|
||||
from .lsp_manager_client import LSPManagerClient
|
||||
from .client_manager import ClientManager
|
||||
from .response_handlers.response_registry import ResponseRegistry
|
||||
|
||||
|
||||
@@ -31,16 +35,16 @@ class LSPManager(ControllerBase):
|
||||
def _init(self):
|
||||
self.provider: Provider = Provider()
|
||||
self.response_cache: ProviderResponseCache = ProviderResponseCache()
|
||||
self.lsp_manager_client: LSPManagerClient = LSPManagerClient()
|
||||
self.client_manager: ClientManager = ClientManager()
|
||||
self.response_registry: ResponseRegistry = ResponseRegistry()
|
||||
|
||||
def _load_widgets(self):
|
||||
self.lsp_manager_ui: LSPManagerUI = LSPManagerUI()
|
||||
self.lsp_manager_ui.connect('create-client', self._on_create_client)
|
||||
self.lsp_manager_ui.connect('close-client', self._on_close_client)
|
||||
self.ui_manager: LSPManagerUI = UIManager()
|
||||
self.ui_manager.connect('create-client', self._on_create_client)
|
||||
self.ui_manager.connect('close-client', self._on_close_client)
|
||||
|
||||
def _do_bind_mapping(self):
|
||||
self.response_cache.set_lsp_manager_client(self.lsp_manager_client)
|
||||
self.response_cache.set_lsp_manager_client(self.client_manager)
|
||||
self.provider.response_cache = self.response_cache
|
||||
self.response_registry.set_event_hub(
|
||||
self.emit, self.emit_to, self.provider
|
||||
@@ -49,39 +53,51 @@ class LSPManager(ControllerBase):
|
||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||
if isinstance(event, Code_Event_Types.RegisterLspClientEvent):
|
||||
self.response_registry.register_handler(event.lang_id, event.handler)
|
||||
self.lsp_manager_ui.add_client_listing(event.lang_id, event.lang_config)
|
||||
self.ui_manager.add_client_listing(event.lang_id, event.lang_config)
|
||||
elif isinstance(event, Code_Event_Types.UnregisterLspClientEvent):
|
||||
self.response_registry.unregister_handler(event.lang_id)
|
||||
self.lsp_manager_ui.remove_client_listing(event.lang_id)
|
||||
self.ui_manager.remove_client_listing(event.lang_id)
|
||||
|
||||
def _on_create_client(self, ui, lang_id: str, workspace_uri: str) -> bool:
|
||||
def _on_create_client(self, ui, lang_id: str, workspace_path: str) -> bool:
|
||||
init_opts = ui.get_init_opts(lang_id)
|
||||
result = self.create_client(lang_id, workspace_uri, init_opts)
|
||||
result = self.create_client(
|
||||
lang_id,
|
||||
workspace_path,
|
||||
init_opts,
|
||||
ui.adddress_entry.get_text(),
|
||||
f"{ int( ui.adddress_port.get_value() ) }"
|
||||
)
|
||||
|
||||
if result:
|
||||
ui.toggle_client_buttons(show_close = True)
|
||||
|
||||
return result
|
||||
|
||||
def _on_close_client(self, ui, lang_id: str) -> bool:
|
||||
result = self.close_client(lang_id)
|
||||
|
||||
if result:
|
||||
ui.toggle_client_buttons(show_close = False)
|
||||
|
||||
return result
|
||||
|
||||
def handle_destroy(self):
|
||||
self.lsp_manager_ui.disconnect_by_func(self._on_create_client)
|
||||
self.lsp_manager_ui.disconnect_by_func(self._on_close_client)
|
||||
self.ui_manager.disconnect_by_func(self._on_create_client)
|
||||
self.ui_manager.disconnect_by_func(self._on_close_client)
|
||||
|
||||
def create_client(
|
||||
self,
|
||||
lang_id: str = "python",
|
||||
workspace_uri: str = "",
|
||||
init_opts: dict = {}
|
||||
lang_id: str,
|
||||
workspace_path: str,
|
||||
init_opts: dict[str, str],
|
||||
address: str,
|
||||
port: str
|
||||
) -> bool:
|
||||
client = self.lsp_manager_client.create_client(
|
||||
lang_id, workspace_uri, init_opts
|
||||
client = self.client_manager.create_client(
|
||||
lang_id, workspace_path, init_opts, address, port
|
||||
)
|
||||
handler = self.response_registry.get_handler(lang_id)
|
||||
self.lsp_manager_client.active_language_id = lang_id
|
||||
self.client_manager.active_language_id = lang_id
|
||||
|
||||
if not client or not handler:
|
||||
logger.error(f"LSP Manager: Either 'client' or 'handler' didn't get created...'")
|
||||
@@ -92,28 +108,46 @@ class LSPManager(ControllerBase):
|
||||
handler.set_response_cache(self.response_cache)
|
||||
|
||||
client.handle_lsp_response = self.server_response
|
||||
client.send_initialize_message(init_opts, "", f"file://{workspace_uri}")
|
||||
|
||||
return True
|
||||
|
||||
def close_client(self, lang_id: str) -> bool:
|
||||
self.lsp_manager_client.close_client(lang_id)
|
||||
controller = self.client_manager.get_active_client()
|
||||
controller.send_shutdown_request()
|
||||
|
||||
def _close():
|
||||
self.client_manager.close_client(lang_id)
|
||||
self.response_registry.close_handler(lang_id)
|
||||
|
||||
return False
|
||||
|
||||
GLib.timeout_add(5000, _close)
|
||||
|
||||
return True
|
||||
|
||||
def server_response(self, lsp_response: LSPResponseTypes):
|
||||
def server_response(self, lsp_response: LSPResponseTypes | dict):
|
||||
logger.debug(f"LSP Response: { lsp_response }")
|
||||
|
||||
if isinstance(lsp_response, LSPResponseRequest):
|
||||
if not self.lsp_manager_client.active_language_id in self.lsp_manager_client.clients:
|
||||
logger.debug(f"No LSP client for '{self.lsp_manager_client.active_language_id}', skipping 'server_response'")
|
||||
if isinstance(lsp_response, dict):
|
||||
if not self.client_manager.active_language_id in self.client_manager.clients:
|
||||
logger.debug(f"No LSP client for '{self.client_manager.active_language_id}', skipping 'server_response'")
|
||||
return
|
||||
|
||||
controller = self.lsp_manager_client.get_active_client()
|
||||
controller = self.client_manager.get_active_client()
|
||||
if "type" in lsp_response and lsp_response["type"] == "connected":
|
||||
controller.send_initialize_message()
|
||||
|
||||
return
|
||||
|
||||
if isinstance(lsp_response, LSPResponseRequest):
|
||||
if not self.client_manager.active_language_id in self.client_manager.clients:
|
||||
logger.debug(f"No LSP client for '{self.client_manager.active_language_id}', skipping 'server_response'")
|
||||
return
|
||||
|
||||
controller = self.client_manager.get_active_client()
|
||||
event = controller.get_event_by_id(lsp_response.id)
|
||||
handler = self.response_registry.get_handler(
|
||||
self.lsp_manager_client.active_language_id, event
|
||||
self.client_manager.active_language_id, event
|
||||
)
|
||||
|
||||
if not handler: return
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
# Python imports
|
||||
import json
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class LSPManagerUI(Gtk.Dialog):
|
||||
__gsignals__ = {
|
||||
'create-client': (GObject.SignalFlags.RUN_LAST, None, (str, str)),
|
||||
'close-client': (GObject.SignalFlags.RUN_LAST, None, (str,)),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super(LSPManagerUI, self).__init__()
|
||||
|
||||
self.client_configs: dict[str, str] = {}
|
||||
|
||||
self.source_view = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_modal(True)
|
||||
self.set_decorated(False)
|
||||
self.set_vexpand(True)
|
||||
self.set_hexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("show", self._handle_show)
|
||||
self.connect("destroy", self._handle_destroy)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
content_area = self.get_content_area()
|
||||
self.main_box = Gtk.Grid()
|
||||
self.path_entry = Gtk.SearchEntry()
|
||||
self.path_bttn = Gtk.FileChooserButton.new(
|
||||
title = "Workspace Folder",
|
||||
action = Gtk.FileChooserAction.SELECT_FOLDER
|
||||
)
|
||||
self.combo_box = Gtk.ComboBoxText()
|
||||
|
||||
self.hide_bttn = Gtk.Button(label = "X")
|
||||
bttn_box = Gtk.Box()
|
||||
self.create_client_bttn = Gtk.Button(label = "Create Language Client")
|
||||
self.close_client_bttn = Gtk.Button(label = "Close Language Client")
|
||||
|
||||
self.path_entry.set_can_focus(False)
|
||||
self.path_entry.set_placeholder_text("Workspace Folder...")
|
||||
self.path_entry.connect("changed", self._path_changed, bttn_box)
|
||||
self.path_bttn.set_halign(Gtk.Align.FILL)
|
||||
|
||||
self.path_bttn.connect("file-set", self._file_set)
|
||||
self.combo_box.connect("changed", self._on_combo_changed)
|
||||
self.hide_bttn_id = self.hide_bttn.connect("clicked", lambda widget: self.hide())
|
||||
self.create_client_bttn.connect("clicked", self._create_client, self.close_client_bttn)
|
||||
self.close_client_bttn.connect("clicked", self._close_client, self.create_client_bttn)
|
||||
|
||||
self.main_box.set_column_spacing(15)
|
||||
self.main_box.set_row_spacing(15)
|
||||
|
||||
bttn_box.pack_start(self.create_client_bttn, False, False, 0)
|
||||
bttn_box.pack_start(self.close_client_bttn, False, False, 0)
|
||||
|
||||
self.main_box.attach(child = self.path_entry, left = 0, top = 0, width = 4, height = 1)
|
||||
self.main_box.attach(child = self.path_bttn, left = 4, top = 0, width = 1, height = 1)
|
||||
self.main_box.attach(child = self.combo_box, left = 5, top = 0, width = 1, height = 1)
|
||||
self.main_box.attach(child = self.hide_bttn, left = 6, top = 0, width = 1, height = 1)
|
||||
self.main_box.attach(child = bttn_box, left = 0, top = 1, width = 1, height = 1)
|
||||
|
||||
content_area.set_vexpand(True)
|
||||
content_area.set_hexpand(True)
|
||||
|
||||
content_area.add(self.main_box)
|
||||
content_area.show_all()
|
||||
self.close_client_bttn.hide()
|
||||
bttn_box.hide()
|
||||
|
||||
def _handle_show(self, widget):
|
||||
GLib.idle_add(self.path_entry.grab_focus)
|
||||
|
||||
def _handle_destroy(self, widget):
|
||||
self.disconnect_by_func(self._show)
|
||||
self.disconnect_by_func(self._handle_destroy)
|
||||
self.path_bttn.disconnect_by_func(self._file_set)
|
||||
self.combo_box.disconnect_by_func(self._on_combo_changed)
|
||||
self.hide_bttn.disconnect(self.hide_bttn_id)
|
||||
self.create_client_bttn.disconnect_by_func(self._create_client)
|
||||
self.close_client_bttn.disconnect_by_func(self._close_client)
|
||||
|
||||
def _map_resize(self, widget, parent):
|
||||
parent_x, parent_y = parent.get_position()
|
||||
parent_width, parent_height = parent.get_size()
|
||||
if parent_width == 0 or parent_height == 0: return
|
||||
|
||||
width = int(parent_width * 0.75)
|
||||
height = int(parent_height * 0.75)
|
||||
|
||||
widget.resize(width, height)
|
||||
|
||||
x = parent_x + (parent_width - width) // 2
|
||||
y = parent_y + (parent_height - height) // 2
|
||||
widget.move(x, y)
|
||||
|
||||
def _path_changed(self, widget, buttons_widget):
|
||||
if not widget.get_text():
|
||||
self.path_bttn.unselect_all()
|
||||
self.path_bttn.emit("file-set")
|
||||
buttons_widget.hide()
|
||||
return
|
||||
|
||||
self.set_source_view_text( self.path_entry.get_text() )
|
||||
buttons_widget.show()
|
||||
|
||||
def _file_set(self, widget):
|
||||
fname = widget.get_filename()
|
||||
fname = "" if not fname else fname
|
||||
self.path_entry.set_text(fname)
|
||||
|
||||
lang_id = self.combo_box.get_active_text()
|
||||
if not lang_id or lang_id not in self.client_configs: return
|
||||
|
||||
self.set_source_view_text(
|
||||
"{workspace.folder}" if not fname else fname
|
||||
)
|
||||
|
||||
def _create_client(self, widget, sibling):
|
||||
if not self.source_view: return
|
||||
|
||||
buffer = self.source_view.get_buffer()
|
||||
lang_id = self.combo_box.get_active_text()
|
||||
|
||||
if not lang_id: return
|
||||
|
||||
workspace_dir = self.path_entry.get_text()
|
||||
self.emit('create-client', lang_id, workspace_dir)
|
||||
|
||||
def _close_client(self, widget, sibling):
|
||||
lang_id = self.combo_box.get_active_text()
|
||||
|
||||
if not lang_id: return
|
||||
self.emit('close-client', lang_id)
|
||||
|
||||
def _on_combo_changed(self, combo: Gtk.ComboBoxText):
|
||||
lang_id = combo.get_active_text()
|
||||
self.set_source_view_text( self.path_entry.get_text() )
|
||||
|
||||
|
||||
def set_source_view_text(self, workspace_dir: str):
|
||||
lang_id = self.combo_box.get_active_text()
|
||||
if not lang_id: return
|
||||
|
||||
json_str = self.client_configs[lang_id].replace("{workspace.folder}", workspace_dir)
|
||||
buffer = self.source_view.get_buffer()
|
||||
|
||||
buffer.set_text(json_str, -1)
|
||||
|
||||
def map_parent_resize_event(self, parent):
|
||||
self.size_allocate_id = parent.connect("size-allocate", lambda w, r: self._map_resize(self, parent))
|
||||
|
||||
def unmap_parent_resize_event(self, parent):
|
||||
parent.disconnect(self.size_allocate_id)
|
||||
|
||||
def set_source_view(self, source_view):
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
lang_manager = GtkSource.LanguageManager()
|
||||
buffer = source_view.get_buffer()
|
||||
language = lang_manager.get_language("json")
|
||||
self.source_view = source_view
|
||||
|
||||
buffer.set_language(language)
|
||||
buffer.set_style_scheme(self.source_view.syntax_theme)
|
||||
|
||||
scrolled_win.set_hexpand(True)
|
||||
scrolled_win.set_vexpand(True)
|
||||
|
||||
scrolled_win.add(self.source_view)
|
||||
self.main_box.attach(child = scrolled_win, left = 0, top = 2, width = 7, height = 1)
|
||||
|
||||
scrolled_win.show_all()
|
||||
|
||||
def add_client_listing(self, lang_id: str, lang_config: str):
|
||||
self.combo_box.append_text(lang_id)
|
||||
self.client_configs[lang_id] = lang_config
|
||||
|
||||
def remove_client_listing(self, lang_id: str):
|
||||
model = self.combo_box.get_model()
|
||||
|
||||
for i, row in enumerate(model):
|
||||
if row[0] == lang_id: # assuming text is in column 0
|
||||
self.combo_box.remove(i)
|
||||
break
|
||||
|
||||
if lang_id in self.client_configs:
|
||||
del self.client_configs[lang_id]
|
||||
|
||||
def get_init_opts(self, lang_id: str) -> dict:
|
||||
if not lang_id or lang_id not in self.client_configs: return {}
|
||||
|
||||
try:
|
||||
lang_config = json.loads(self.client_configs[lang_id])
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Invalid JSON for {lang_id}: {e}")
|
||||
return {}
|
||||
|
||||
return lang_config.get("initialization-options", {})
|
||||
|
||||
def toggle_client_buttons(self, show_close: bool):
|
||||
self.create_client_bttn.set_visible(not show_close)
|
||||
self.close_client_bttn.set_visible(show_close)
|
||||
@@ -3,6 +3,7 @@
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"support": "",
|
||||
"pre_launch": true,
|
||||
"autoload": false,
|
||||
"requests": {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Code_Event_Types
|
||||
|
||||
|
||||
|
||||
class ClientManagerEventsMixin:
|
||||
def _get_controller(self, lang_id, action: str):
|
||||
controller = self.clients.get(lang_id)
|
||||
if not controller:
|
||||
logger.debug(f"No LSP client for '{lang_id}', skipping {action}...")
|
||||
|
||||
return controller
|
||||
|
||||
def _uri(self, fpath: str) -> str:
|
||||
return fpath if fpath.startswith("file://") else f"file://{fpath}"
|
||||
|
||||
def _text(self, buffer, *, hidden = False):
|
||||
return buffer.get_text(
|
||||
*buffer.get_bounds(),
|
||||
include_hidden_chars=hidden
|
||||
)
|
||||
|
||||
def _version(self, controller, uri, bump = False):
|
||||
if bump:
|
||||
controller.doc_vers[uri] = controller.doc_vers.get(uri, -1) + 1
|
||||
|
||||
return controller.doc_vers.get(uri, 0)
|
||||
|
||||
def _activate(self, lang_id):
|
||||
self.active_language_id = lang_id
|
||||
|
||||
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
||||
f = event.file
|
||||
if not (c := self._get_controller(f.ftype, "didOpen")): return
|
||||
|
||||
uri = self._uri(f.fpath)
|
||||
self._activate(f.ftype)
|
||||
|
||||
c._lsp_did_open({
|
||||
"uri": uri,
|
||||
"language_id": f.ftype,
|
||||
"text": self._text(f.buffer),
|
||||
})
|
||||
|
||||
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
||||
f = event.file
|
||||
if not (c := self._get_controller(f.ftype, "didClose")): return
|
||||
|
||||
uri = self._uri(f.fpath)
|
||||
c.doc_vers.pop(uri, None)
|
||||
c._lsp_did_close({"uri": uri})
|
||||
|
||||
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
||||
f = event.file
|
||||
if not (c := self._get_controller(f.ftype, "didSave")): return
|
||||
|
||||
uri = self._uri(f.fpath)
|
||||
self._activate(f.ftype)
|
||||
|
||||
c._lsp_did_save({
|
||||
"uri": uri,
|
||||
"text": self._text(f.buffer),
|
||||
})
|
||||
|
||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
||||
f = event.file
|
||||
if not (c := self._get_controller(f.ftype, "didChange")): return
|
||||
|
||||
uri = self._uri(f.fpath)
|
||||
self._activate(f.ftype)
|
||||
|
||||
version = self._version(c, uri, bump = True)
|
||||
|
||||
c._lsp_did_change({
|
||||
"uri": uri,
|
||||
"language_id": f.ftype,
|
||||
"version": version,
|
||||
"text": self._text(f.buffer, hidden = True),
|
||||
})
|
||||
|
||||
it = f.buffer.get_iter_at_mark(f.buffer.get_insert())
|
||||
self._set_cache_refresh_trigger(
|
||||
f.ftype, f.fpath,
|
||||
it.get_line(),
|
||||
it.get_line_offset() + 1
|
||||
)
|
||||
|
||||
def _iter_pos(self, it):
|
||||
return it.get_line(), it.get_line_offset()
|
||||
|
||||
def process_file_text_inserted(self, event: Code_Event_Types.TextInsertedEvent):
|
||||
f = event.file
|
||||
if not (c := self._get_controller(f.ftype, "didChange")): return
|
||||
|
||||
uri = self._uri(f.fpath)
|
||||
self._activate(f.ftype)
|
||||
|
||||
start_it = event.location.copy()
|
||||
end_it = event.location.copy()
|
||||
|
||||
if event.length > 1:
|
||||
start_it.backward_chars(event.length)
|
||||
|
||||
sl, sc = self._iter_pos(start_it)
|
||||
el, ec = self._iter_pos(end_it)
|
||||
sc -= 0 if event.length > 1 else 1
|
||||
ec -= 1
|
||||
|
||||
version = self._version(c, uri, bump = True)
|
||||
|
||||
c._lsp_did_change_range({
|
||||
"uri": uri,
|
||||
"language_id": f.ftype,
|
||||
"version": version,
|
||||
"text": event.text,
|
||||
"line": sl,
|
||||
"column": sc,
|
||||
"end_line": el,
|
||||
"end_column": ec,
|
||||
})
|
||||
|
||||
it = event.buffer.get_iter_at_mark(event.buffer.get_insert())
|
||||
self._set_cache_refresh_trigger(
|
||||
f.ftype, f.fpath, *self._iter_pos(it)
|
||||
)
|
||||
|
||||
def process_file_delete_range(self, event: Code_Event_Types.DeleteRangeEvent):
|
||||
f = event.file
|
||||
if not (c := self._get_controller(f.ftype, "didChange")): return
|
||||
|
||||
uri = self._uri(f.fpath)
|
||||
self._activate(f.ftype)
|
||||
|
||||
start_it, end_it = event.start.copy(), event.end.copy()
|
||||
if start_it.compare(end_it) > 0:
|
||||
start_it, end_it = end_it, start_it
|
||||
|
||||
sl, sc = self._iter_pos(start_it)
|
||||
el, ec = self._iter_pos(end_it)
|
||||
|
||||
version = self._version(c, uri, bump = True)
|
||||
|
||||
c._lsp_did_change_range({
|
||||
"uri": uri,
|
||||
"language_id": f.ftype,
|
||||
"version": version,
|
||||
"text": "",
|
||||
"line": sl,
|
||||
"column": sc,
|
||||
"end_line": el,
|
||||
"end_column": ec,
|
||||
})
|
||||
|
||||
it = event.buffer.get_iter_at_mark(event.buffer.get_insert())
|
||||
self._set_cache_refresh_trigger(
|
||||
f.ftype, f.fpath, *self._iter_pos(it)
|
||||
)
|
||||
|
||||
def _request(self, method, lang_id, fpath, **extra):
|
||||
if not (c := self._get_controller(lang_id, method)): return
|
||||
|
||||
uri = self._uri(fpath)
|
||||
self._activate(lang_id)
|
||||
|
||||
payload = {
|
||||
"uri": uri,
|
||||
"language_id": lang_id,
|
||||
"version": self._version(c, uri),
|
||||
**extra
|
||||
}
|
||||
|
||||
getattr(c, method)(payload)
|
||||
|
||||
def process_definition(self, lang_id, fpath, line, column):
|
||||
self._request("_lsp_definition", lang_id, fpath, line = line, column = column)
|
||||
|
||||
def process_implementation_definition(self, lang_id, fpath, line, column):
|
||||
self._request("_lsp_implementation", lang_id, fpath, line = line, column = column)
|
||||
|
||||
def process_references_definition(self, lang_id, fpath, line, column):
|
||||
self._request("_lsp_references", lang_id, fpath, line = line, column = column)
|
||||
|
||||
def process_completion_request(self, lang_id, fpath, line, column):
|
||||
self._request("_lsp_completion", lang_id, fpath, line = line, column = column)
|
||||
|
||||
def _set_cache_refresh_trigger(self, lang_id, fpath, line, column):
|
||||
self.process_completion_request(lang_id, fpath, line, column)
|
||||
@@ -1,144 +0,0 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Code_Event_Types
|
||||
|
||||
|
||||
|
||||
class LSPClientEventsMixin:
|
||||
|
||||
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
||||
lang_id = event.file.ftype
|
||||
if lang_id not in self.clients:
|
||||
logger.debug(f"No LSP client for '{lang_id}', skipping didOpen")
|
||||
return
|
||||
|
||||
controller = self.clients[lang_id]
|
||||
fpath = event.file.fpath
|
||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||
buffer = event.file.buffer
|
||||
text = buffer.get_text(*buffer.get_bounds())
|
||||
self.active_language_id = lang_id
|
||||
|
||||
controller._lsp_did_open({
|
||||
"uri": uri,
|
||||
"language_id": lang_id,
|
||||
"text": text
|
||||
})
|
||||
|
||||
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
||||
lang_id = event.file.ftype
|
||||
if lang_id not in self.clients:
|
||||
logger.debug(f"No LSP client for '{lang_id}', skipping didClose")
|
||||
return
|
||||
|
||||
controller = self.clients[lang_id]
|
||||
fpath = event.file.fpath
|
||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||
|
||||
controller._lsp_did_close({"uri": uri})
|
||||
|
||||
def process_file_save(self, event: Code_Event_Types.SavedFileEvent):
|
||||
lang_id = event.file.ftype
|
||||
if lang_id not in self.clients:
|
||||
logger.debug(f"No LSP client for '{lang_id}', skipping didSave")
|
||||
return
|
||||
|
||||
controller = self.clients[lang_id]
|
||||
fpath = event.file.fpath
|
||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||
buffer = event.file.buffer
|
||||
text = buffer.get_text(*buffer.get_bounds())
|
||||
self.active_language_id = lang_id
|
||||
|
||||
controller._lsp_did_save({"uri": uri, "text": text})
|
||||
|
||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
||||
self._clear_delayed_cache_refresh_trigger()
|
||||
|
||||
lang_id = event.file.ftype
|
||||
if lang_id not in self.clients:
|
||||
logger.debug(f"No LSP client for '{lang_id}', skipping didChange")
|
||||
return
|
||||
|
||||
controller = self.clients[lang_id]
|
||||
fpath = event.file.fpath
|
||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||
buffer = event.file.buffer
|
||||
text = buffer.get_text(*buffer.get_bounds())
|
||||
self.active_language_id = lang_id
|
||||
|
||||
controller._lsp_did_change({
|
||||
"uri": uri,
|
||||
"language_id": lang_id,
|
||||
"version": 1,
|
||||
"text": text
|
||||
})
|
||||
|
||||
iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||
line = iter.get_line()
|
||||
column = iter.get_line_offset()
|
||||
self._set_cache_refresh_trigger(
|
||||
lang_id, fpath, line, column
|
||||
)
|
||||
|
||||
|
||||
def process_goto_definition(
|
||||
self, lang_id: str, fpath: str, line: int, column: int
|
||||
):
|
||||
if lang_id not in self.clients:
|
||||
logger.debug(f"No LSP client for '{lang_id}', skipping goto definition")
|
||||
return
|
||||
|
||||
controller = self.clients[lang_id]
|
||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||
self.active_language_id = lang_id
|
||||
|
||||
controller._lsp_definition({
|
||||
"uri": uri,
|
||||
"language_id": lang_id,
|
||||
"version": 1,
|
||||
"line": line,
|
||||
"column": column
|
||||
})
|
||||
|
||||
def process_completion_request(
|
||||
self, lang_id: str, fpath: str, line: int, column: int
|
||||
):
|
||||
if lang_id not in self.clients:
|
||||
logger.debug(f"No LSP client for '{lang_id}', skipping completion")
|
||||
return
|
||||
|
||||
controller = self.clients[lang_id]
|
||||
uri = f"file://{fpath}" if not fpath.startswith("file://") else fpath
|
||||
self.active_language_id = lang_id
|
||||
|
||||
controller._lsp_completion({
|
||||
"uri": uri,
|
||||
"language_id": lang_id,
|
||||
"version": 1,
|
||||
"line": line,
|
||||
"column": column
|
||||
})
|
||||
|
||||
|
||||
def _clear_delayed_cache_refresh_trigger(self):
|
||||
if self._cache_refresh_timeout_id:
|
||||
GLib.source_remove(self._cache_refresh_timeout_id)
|
||||
|
||||
def _set_cache_refresh_trigger(
|
||||
self, lang_id: str, fpath: str, line: int, column: int
|
||||
):
|
||||
def trigger_cache_refresh(lang_id, fpath, line, column):
|
||||
self._cache_refresh_timeout_id = None
|
||||
self.process_completion_request(
|
||||
lang_id, fpath, line, column
|
||||
)
|
||||
return False
|
||||
|
||||
self._cache_refresh_timeout_id = GLib.timeout_add(1500, trigger_cache_refresh, lang_id, fpath, line, column)
|
||||
@@ -0,0 +1,72 @@
|
||||
# Python imports
|
||||
import json
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
from gi.repository import GObject
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class UIManagerClientsMixin:
|
||||
def _create_client(self, widget, sibling):
|
||||
if not self.source_view: return
|
||||
|
||||
buffer = self.source_view.get_buffer()
|
||||
lang_id = self.combo_box.get_active_text()
|
||||
|
||||
if not lang_id: return
|
||||
|
||||
workspace_dir = self.path_entry.get_text()
|
||||
self.emit('create-client', lang_id, workspace_dir)
|
||||
|
||||
def _close_client(self, widget, sibling):
|
||||
lang_id = self.combo_box.get_active_text()
|
||||
|
||||
if not lang_id: return
|
||||
|
||||
self.emit('close-client', lang_id)
|
||||
|
||||
def set_source_view_text(self, workspace_dir: str):
|
||||
lang_id = self.combo_box.get_active_text()
|
||||
|
||||
if not lang_id: return
|
||||
|
||||
json_str = self.client_configs[lang_id]\
|
||||
.replace("{workspace.folder}", workspace_dir)\
|
||||
.replace("{user.home}", self._USER_HOME)
|
||||
|
||||
self.source_view.get_buffer().set_text(json_str, -1)
|
||||
|
||||
def add_client_listing(self, lang_id: str, lang_config: str):
|
||||
self.combo_box.append_text(lang_id)
|
||||
self.client_configs[lang_id] = lang_config
|
||||
|
||||
def remove_client_listing(self, lang_id: str):
|
||||
model = self.combo_box.get_model()
|
||||
|
||||
for i, row in enumerate(model):
|
||||
if row[0] == lang_id:
|
||||
self.combo_box.remove(i)
|
||||
break
|
||||
|
||||
self.client_configs.pop(lang_id, None)
|
||||
|
||||
def toggle_client_buttons(self, show_close: bool):
|
||||
self.create_client_bttn.set_visible(not show_close)
|
||||
self.close_client_bttn.set_visible(show_close)
|
||||
|
||||
def get_init_opts(self, lang_id: str) -> dict:
|
||||
if not lang_id or lang_id not in self.client_configs: return {}
|
||||
|
||||
try:
|
||||
buffer = self.source_view.get_buffer()
|
||||
json_str = buffer.get_text(*buffer.get_bounds(), -1)
|
||||
lang_config = json.loads(json_str)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Invalid JSON for {lang_id}: {e}")
|
||||
return {}
|
||||
|
||||
return lang_config.get("initialization-options", {})
|
||||
@@ -0,0 +1,77 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class UIManagerEventsMixin:
|
||||
def _setup_signals(self):
|
||||
self.connect("show", self._handle_show)
|
||||
self.connect("destroy", self._handle_destroy)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _handle_show(self, widget):
|
||||
GLib.idle_add(self.path_entry.grab_focus)
|
||||
|
||||
def _handle_destroy(self, widget):
|
||||
self.disconnect_by_func(self._handle_show)
|
||||
self.disconnect_by_func(self._handle_destroy)
|
||||
self.path_bttn.disconnect_by_func(self._file_set)
|
||||
self.combo_box.disconnect_by_func(self._on_combo_changed)
|
||||
self.hide_bttn.disconnect(self.hide_bttn_id)
|
||||
self.create_client_bttn.disconnect_by_func(self._create_client)
|
||||
self.close_client_bttn.disconnect_by_func(self._close_client)
|
||||
|
||||
def _map_resize(self, widget, parent):
|
||||
parent_x, \
|
||||
parent_y = parent.get_position()
|
||||
parent_width, \
|
||||
parent_height = parent.get_size()
|
||||
|
||||
if parent_width == 0 or parent_height == 0: return
|
||||
|
||||
width = int(parent_width * 0.75)
|
||||
height = int(parent_height * 0.75)
|
||||
|
||||
widget.resize(width, height)
|
||||
|
||||
x = parent_x + (parent_width - width) // 2
|
||||
y = parent_y + (parent_height - height) // 2
|
||||
widget.move(x, y)
|
||||
|
||||
def _path_changed(self, widget, buttons_widget):
|
||||
if not widget.get_text():
|
||||
self.path_bttn.unselect_all()
|
||||
self.path_bttn.emit("file-set")
|
||||
buttons_widget.hide()
|
||||
return
|
||||
|
||||
self.set_source_view_text( self.path_entry.get_text() )
|
||||
buttons_widget.show()
|
||||
|
||||
def _file_set(self, widget):
|
||||
fname = widget.get_filename()
|
||||
fname = "" if not fname else fname
|
||||
self.path_entry.set_text(fname)
|
||||
|
||||
lang_id = self.combo_box.get_active_text()
|
||||
if not lang_id or lang_id not in self.client_configs: return
|
||||
|
||||
self.set_source_view_text(
|
||||
"{workspace.folder}" if not fname else fname
|
||||
)
|
||||
|
||||
def _on_combo_changed(self, combo: Gtk.ComboBoxText):
|
||||
lang_id = combo.get_active_text()
|
||||
self.set_source_view_text( self.path_entry.get_text() )
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# 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 ..config import get_lsp_host_addr, get_lsp_host_port
|
||||
|
||||
|
||||
|
||||
class UIManagerSetupMixin:
|
||||
def _setup_styling(self):
|
||||
self.set_modal(True)
|
||||
self.set_decorated(False)
|
||||
self.set_vexpand(True)
|
||||
self.set_hexpand(True)
|
||||
|
||||
def _load_widgets(self):
|
||||
content_area = self.get_content_area()
|
||||
self.main_box = Gtk.Grid()
|
||||
self.path_entry = Gtk.SearchEntry()
|
||||
self.path_bttn = Gtk.FileChooserButton.new(
|
||||
title = "Workspace Folder",
|
||||
action = Gtk.FileChooserAction.SELECT_FOLDER
|
||||
)
|
||||
self.combo_box = Gtk.ComboBoxText()
|
||||
self.hide_bttn = Gtk.Button(label = "X")
|
||||
|
||||
self.adddress_entry = Gtk.Entry()
|
||||
adjustment = Gtk.Adjustment(
|
||||
value = get_lsp_host_port(),
|
||||
lower = 1,
|
||||
upper = 65535,
|
||||
step_increment = 1,
|
||||
page_increment = 10,
|
||||
page_size = 0
|
||||
)
|
||||
|
||||
self.adddress_port = Gtk.SpinButton()
|
||||
self.adddress_port.set_adjustment(adjustment)
|
||||
self.adddress_port.set_digits(0) # integers only
|
||||
|
||||
bttn_box = Gtk.Box()
|
||||
self.create_client_bttn = Gtk.Button(label = "Create Language Client")
|
||||
self.close_client_bttn = Gtk.Button(label = "Close Language Client")
|
||||
|
||||
self.path_entry.set_can_focus(False)
|
||||
self.path_entry.set_placeholder_text("Workspace Folder...")
|
||||
self.path_entry.connect("changed", self._path_changed, bttn_box)
|
||||
self.path_bttn.set_halign(Gtk.Align.FILL)
|
||||
|
||||
self.adddress_entry.set_placeholder_text("Address...")
|
||||
self.adddress_entry.set_text( get_lsp_host_addr() )
|
||||
|
||||
self.path_bttn.connect("file-set", self._file_set)
|
||||
self.combo_box.connect("changed", self._on_combo_changed)
|
||||
self.hide_bttn_id = self.hide_bttn.connect("clicked", lambda widget: self.hide())
|
||||
self.create_client_bttn.connect("clicked", self._create_client, self.close_client_bttn)
|
||||
self.close_client_bttn.connect("clicked", self._close_client, self.create_client_bttn)
|
||||
|
||||
self.main_box.set_column_spacing(15)
|
||||
self.main_box.set_row_spacing(15)
|
||||
|
||||
bttn_box.pack_start(self.create_client_bttn, False, False, 0)
|
||||
bttn_box.pack_start(self.close_client_bttn, False, False, 0)
|
||||
|
||||
self.main_box.attach(child = self.path_entry, left = 0, top = 0, width = 4, height = 1)
|
||||
self.main_box.attach(child = self.path_bttn, left = 4, top = 0, width = 1, height = 1)
|
||||
self.main_box.attach(child = self.combo_box, left = 5, top = 0, width = 1, height = 1)
|
||||
self.main_box.attach(child = self.hide_bttn, left = 6, top = 0, width = 1, height = 1)
|
||||
|
||||
self.main_box.attach(child = self.adddress_entry, left = 0, top = 1, width = 2, height = 1)
|
||||
self.main_box.attach(child = self.adddress_port, left = 2, top = 1, width = 2, height = 1)
|
||||
self.main_box.attach(child = bttn_box, left = 4, top = 1, width = 3, height = 1)
|
||||
|
||||
content_area.set_vexpand(True)
|
||||
content_area.set_hexpand(True)
|
||||
|
||||
content_area.add(self.main_box)
|
||||
content_area.show_all()
|
||||
self.close_client_bttn.hide()
|
||||
bttn_box.hide()
|
||||
|
||||
def set_source_view(self, scrolled_win, source_view):
|
||||
lang_manager = GtkSource.LanguageManager()
|
||||
buffer = source_view.get_buffer()
|
||||
language = lang_manager.get_language("json")
|
||||
self.source_view = source_view
|
||||
|
||||
buffer.set_language(language)
|
||||
buffer.set_style_scheme(self.source_view.syntax_theme)
|
||||
|
||||
self.main_box.attach(scrolled_win, 0, 2, 7, 1)
|
||||
@@ -12,6 +12,7 @@ from libs.dto.states import SourceViewStates
|
||||
from plugins.plugin_types import PluginCode
|
||||
|
||||
from .dto.code import events as lsp_events
|
||||
from .commands import Commands
|
||||
from .lsp_manager import LSPManager
|
||||
|
||||
|
||||
@@ -35,23 +36,11 @@ class Plugin(PluginCode):
|
||||
|
||||
window = self.request_ui_element("main-window")
|
||||
|
||||
lsp_manager.lsp_manager_ui.map_parent_resize_event(window)
|
||||
lsp_manager.ui_manager.map_parent_resize_event(window)
|
||||
|
||||
event = Event_Factory.create_event("register_command",
|
||||
command_name = "LSP Manager",
|
||||
command = Handler,
|
||||
binding_mode = "released",
|
||||
binding = ["<Shift><Control>l", "<Control>g", "<Control>i"]
|
||||
)
|
||||
self.emit_to("source_views", event)
|
||||
self._manage_signals("register_command")
|
||||
|
||||
event = Event_Factory.create_event(
|
||||
"register_provider",
|
||||
provider_name = "LSP Completer",
|
||||
provider = lsp_manager.provider,
|
||||
language_ids = []
|
||||
)
|
||||
self.emit_to("completion", event)
|
||||
self._manage_provider("register_provider")
|
||||
|
||||
event = Event_Factory.create_event(
|
||||
"create_source_view",
|
||||
@@ -59,8 +48,8 @@ class Plugin(PluginCode):
|
||||
)
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
source_view = event.response
|
||||
lsp_manager.lsp_manager_ui.set_source_view(source_view)
|
||||
scrolled_win, source_view = event.response
|
||||
lsp_manager.ui_manager.set_source_view(scrolled_win, source_view)
|
||||
|
||||
def unload(self):
|
||||
Event_Factory.unregister_events( lsp_events.__dict__.items() )
|
||||
@@ -69,56 +58,61 @@ class Plugin(PluginCode):
|
||||
|
||||
window = self.request_ui_element("main-window")
|
||||
|
||||
lsp_manager.lsp_manager_ui.unmap_parent_resize_event(window)
|
||||
lsp_manager.ui_manager.unmap_parent_resize_event(window)
|
||||
|
||||
event = Event_Factory.create_event("unregister_command",
|
||||
command_name = "LSP Manager",
|
||||
command = Handler,
|
||||
self._manage_signals("unregister_command")
|
||||
|
||||
self._manage_provider("unregister_provider")
|
||||
|
||||
lsp_manager.handle_destroy()
|
||||
|
||||
def _manage_signals(self, action: str):
|
||||
_commands = Commands
|
||||
_commands.lsp_manager = lsp_manager
|
||||
|
||||
event = Event_Factory.create_event(action,
|
||||
command_name = "lsp_manager_toggle",
|
||||
command = _commands.lsp_manager_toggle,
|
||||
binding_mode = "released",
|
||||
binding = ["<Shift><Control>l", "<Control>g", "<Control>i"]
|
||||
binding = "<Shift><Control>l"
|
||||
)
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
event = Event_Factory.create_event(action,
|
||||
command_name = "lsp_references",
|
||||
command = _commands.lsp_references,
|
||||
binding_mode = "released",
|
||||
binding = "<Control>i"
|
||||
)
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
event = Event_Factory.create_event(action,
|
||||
command_name = "lsp_implementation",
|
||||
command = _commands.lsp_implementation,
|
||||
binding_mode = "released",
|
||||
binding = "<Shift><Control>i"
|
||||
)
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
event = Event_Factory.create_event(action,
|
||||
command_name = "lsp_definition",
|
||||
command = _commands.lsp_definition,
|
||||
binding_mode = "released",
|
||||
binding = "<Control>g"
|
||||
)
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
def _manage_provider(self, action: str):
|
||||
event = Event_Factory.create_event(
|
||||
"unregister_provider",
|
||||
provider_name = "LSP Completer"
|
||||
action,
|
||||
provider_name = "LSP Completer",
|
||||
provider = lsp_manager.provider,
|
||||
language_ids = []
|
||||
)
|
||||
self.emit_to("completion", event)
|
||||
|
||||
lsp_manager.handle_destroy()
|
||||
|
||||
def run(self):
|
||||
...
|
||||
|
||||
def generate_plugin_element(self):
|
||||
...
|
||||
|
||||
|
||||
class Handler:
|
||||
@staticmethod
|
||||
def execute(
|
||||
view: any,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
logger.debug("Command: LSP Manager")
|
||||
|
||||
char_str = args[0]
|
||||
if char_str in ["g", "i"]:
|
||||
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()
|
||||
|
||||
if char_str == "g":
|
||||
lsp_manager.lsp_manager_client.process_goto_definition(
|
||||
file.ftype, file.fpath, line, column
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
if char_str == "i":
|
||||
return
|
||||
|
||||
lsp_manager.lsp_manager_ui.hide() if lsp_manager.lsp_manager_ui.is_visible() else lsp_manager.lsp_manager_ui.show()
|
||||
|
||||
@@ -32,20 +32,7 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
||||
return "LSP Code Completion"
|
||||
|
||||
def do_match(self, context):
|
||||
# Note: If provider is in interactive activation then need to check
|
||||
# view focus as otherwise non focus views start trying to grab it.
|
||||
# completion = context.get_property("completion")
|
||||
# if not completion.get_view().has_focus(): return
|
||||
|
||||
iter = self.response_cache.get_iter_correctly(context)
|
||||
iter.backward_char()
|
||||
ch = iter.get_char()
|
||||
|
||||
# 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()):
|
||||
return False
|
||||
|
||||
buffer = iter.get_buffer()
|
||||
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
|
||||
@@ -68,6 +55,7 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
||||
# return GtkSource.CompletionActivation.NONE
|
||||
return GtkSource.CompletionActivation.USER_REQUESTED
|
||||
# return GtkSource.CompletionActivation.INTERACTIVE
|
||||
# return GtkSource.CompletionActivation.USER_REQUESTED | GtkSource.CompletionActivation.INTERACTIVE
|
||||
|
||||
def do_populate(self, context):
|
||||
results = self.response_cache.filter_with_context(context)
|
||||
|
||||
@@ -33,9 +33,13 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
||||
if self.lsp_manager_client:
|
||||
self.lsp_manager_client.process_file_save(event)
|
||||
|
||||
def process_file_change(self, event):
|
||||
def process_file_text_inserted(self, event):
|
||||
if self.lsp_manager_client:
|
||||
self.lsp_manager_client.process_file_change(event)
|
||||
self.lsp_manager_client.process_file_text_inserted(event)
|
||||
|
||||
def process_file_delete_range(self, event):
|
||||
if self.lsp_manager_client:
|
||||
self.lsp_manager_client.process_file_delete_range(event)
|
||||
|
||||
def filter(self, word: str) -> list[dict]:
|
||||
return []
|
||||
|
||||
@@ -17,10 +17,18 @@ class DefaultHandler(BaseHandler):
|
||||
|
||||
def handle(self, method: str, response, controller):
|
||||
match method:
|
||||
case "initialize":
|
||||
controller.send_initialized_notification()
|
||||
case "shutdown":
|
||||
controller.send_exit_notification()
|
||||
case "textDocument/completion":
|
||||
self._handle_completion(response)
|
||||
case "textDocument/definition":
|
||||
self._handle_definition(response, controller)
|
||||
case "textDocument/references":
|
||||
...
|
||||
case "textDocument/implementation":
|
||||
...
|
||||
case "textDocument/publishDiagnostics":
|
||||
self._handle_diagnostics(response)
|
||||
|
||||
@@ -130,5 +138,5 @@ class DefaultHandler(BaseHandler):
|
||||
view = view,
|
||||
provider = self.context._provider
|
||||
)
|
||||
self.emit_to("completion", event)
|
||||
|
||||
self.emit_to("completion", event)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# Python imports
|
||||
from os import path
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
from gi.repository import GObject
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from .mixins.ui_manager_setup_mixin import UIManagerSetupMixin
|
||||
from .mixins.ui_manager_events_mixin import UIManagerEventsMixin
|
||||
from .mixins.ui_manager_clients_mixin import UIManagerClientsMixin
|
||||
|
||||
|
||||
|
||||
class UIManager(
|
||||
Gtk.Dialog,
|
||||
UIManagerSetupMixin,
|
||||
UIManagerEventsMixin,
|
||||
UIManagerClientsMixin
|
||||
):
|
||||
__gsignals__ = {
|
||||
'create-client': (GObject.SignalFlags.RUN_LAST, None, (str, str)),
|
||||
'close-client': (GObject.SignalFlags.RUN_LAST, None, (str,)),
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
super(UIManager, self).__init__()
|
||||
self._USER_HOME = path.expanduser("~")
|
||||
self.client_configs: dict[str, str] = {}
|
||||
self.source_view = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def map_parent_resize_event(self, parent):
|
||||
self.size_allocate_id = parent.connect("size-allocate", lambda w, r: self._map_resize(self, parent))
|
||||
|
||||
def unmap_parent_resize_event(self, parent):
|
||||
parent.disconnect(self.size_allocate_id)
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"info": "https://github.com/python-lsp/python-lsp-server",
|
||||
"command": "lsp-ws-proxy -- pylsp",
|
||||
"alt-command": "pylsp",
|
||||
"alt-command2": "lsp-ws-proxy --listen 4114 -- pylsp",
|
||||
"alt-command3": "pylsp --ws --port 4114",
|
||||
"socket": "ws://127.0.0.1:9999/python",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=pylsp",
|
||||
"initialization-options": {
|
||||
"pylsp": {
|
||||
"rope": {
|
||||
"ropeFolder": "{user.home}/.config/newton/lsps/ropeproject"
|
||||
},
|
||||
"plugins": {
|
||||
"ruff": {
|
||||
"enabled": true,
|
||||
"extendSelect": ["I"],
|
||||
"lineLength": 80
|
||||
},
|
||||
"pycodestyle": {
|
||||
"enabled": false
|
||||
},
|
||||
"pyflakes": {
|
||||
"enabled": false
|
||||
},
|
||||
"pylint": {
|
||||
"enabled": true
|
||||
},
|
||||
"mccabe": {
|
||||
"enabled": false
|
||||
},
|
||||
"pylsp_rope": {
|
||||
"rename": false
|
||||
},
|
||||
"rope_rename": {
|
||||
"enabled": false
|
||||
},
|
||||
"rope_autoimport": {
|
||||
"enabled": true
|
||||
},
|
||||
"rope_completion": {
|
||||
"enabled": false,
|
||||
"eager": false
|
||||
},
|
||||
"jedi_rename": {
|
||||
"enabled": true
|
||||
},
|
||||
"jedi_completion": {
|
||||
"enabled": true,
|
||||
"include_class_objects": true,
|
||||
"include_function_objects": true,
|
||||
"fuzzy": false
|
||||
},
|
||||
"jedi": {
|
||||
"root_dir": "file://{workspace.folder}",
|
||||
"extra_paths": [
|
||||
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"python - jedi-language-server": {
|
||||
"hidden": true,
|
||||
"info": "https://pypi.org/project/jedi-language-server/",
|
||||
"command": "jedi-language-server",
|
||||
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
|
||||
"socket": "ws://127.0.0.1:9999/python",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
|
||||
"initialization-options": {
|
||||
"jediSettings": {
|
||||
"autoImportModules": [],
|
||||
"caseInsensitiveCompletion": true,
|
||||
"debug": false
|
||||
},
|
||||
"completion": {
|
||||
"disableSnippets": false,
|
||||
"resolveEagerly": false,
|
||||
"ignorePatterns": []
|
||||
},
|
||||
"markupKindPreferred": "markdown",
|
||||
"workspace": {
|
||||
"extraPaths": [
|
||||
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
|
||||
],
|
||||
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
|
||||
"symbols": {
|
||||
"ignoreFolders": [
|
||||
".nox",
|
||||
".tox",
|
||||
".venv",
|
||||
"__pycache__",
|
||||
"venv"
|
||||
],
|
||||
"maxSymbols": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,9 +47,16 @@
|
||||
},
|
||||
"jedi_completion": {
|
||||
"enabled": true,
|
||||
"fuzzy": true,
|
||||
"include_params": false,
|
||||
"include_class_objects": true,
|
||||
"include_function_objects": true,
|
||||
"fuzzy": false
|
||||
"include_function_objects": true
|
||||
},
|
||||
"jedi_signature_help": {
|
||||
"enabled": true
|
||||
},
|
||||
"jedi_references": {
|
||||
"enabled": true
|
||||
},
|
||||
"jedi": {
|
||||
"root_dir": "file://{workspace.folder}",
|
||||
@@ -60,41 +67,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"python - jedi-language-server": {
|
||||
"hidden": true,
|
||||
"info": "https://pypi.org/project/jedi-language-server/",
|
||||
"command": "jedi-language-server",
|
||||
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
|
||||
"socket": "ws://127.0.0.1:9999/python",
|
||||
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
|
||||
"initialization-options": {
|
||||
"jediSettings": {
|
||||
"autoImportModules": [],
|
||||
"caseInsensitiveCompletion": true,
|
||||
"debug": false
|
||||
},
|
||||
"completion": {
|
||||
"disableSnippets": false,
|
||||
"resolveEagerly": false,
|
||||
"ignorePatterns": []
|
||||
},
|
||||
"markupKindPreferred": "markdown",
|
||||
"workspace": {
|
||||
"extraPaths": [
|
||||
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
|
||||
],
|
||||
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
|
||||
"symbols": {
|
||||
"ignoreFolders": [
|
||||
".nox",
|
||||
".tox",
|
||||
".venv",
|
||||
"__pycache__",
|
||||
"venv"
|
||||
],
|
||||
"maxSymbols": 20
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
plugins/code/ui/code_fold/__init__.py
Normal file
3
plugins/code/ui/code_fold/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Module
|
||||
"""
|
||||
3
plugins/code/ui/code_fold/__main__.py
Normal file
3
plugins/code/ui/code_fold/__main__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Package
|
||||
"""
|
||||
86
plugins/code/ui/code_fold/fold_types.py
Normal file
86
plugins/code/ui/code_fold/fold_types.py
Normal file
@@ -0,0 +1,86 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
FOLD_NODES = {
|
||||
"python": {
|
||||
"function_definition",
|
||||
"class_definition",
|
||||
"if_statement",
|
||||
"for_statement",
|
||||
"while_statement",
|
||||
"with_statement",
|
||||
"try_statement",
|
||||
},
|
||||
"python3": {
|
||||
"function_definition",
|
||||
"class_definition",
|
||||
"if_statement",
|
||||
"for_statement",
|
||||
"while_statement",
|
||||
"with_statement",
|
||||
"try_statement",
|
||||
},
|
||||
"javascript": {
|
||||
"function_declaration",
|
||||
"class_declaration",
|
||||
"if_statement",
|
||||
"for_statement",
|
||||
"while_statement",
|
||||
"switch_statement",
|
||||
"try_statement",
|
||||
},
|
||||
"html": {
|
||||
"element",
|
||||
"attribute",
|
||||
},
|
||||
"css": {
|
||||
"rule_set",
|
||||
"selector",
|
||||
"declaration",
|
||||
},
|
||||
"json": {
|
||||
"object",
|
||||
"array",
|
||||
},
|
||||
"java": {
|
||||
"class_declaration",
|
||||
"method_declaration",
|
||||
"constructor_declaration",
|
||||
"if_statement",
|
||||
"for_statement",
|
||||
"while_statement",
|
||||
"switch_expression",
|
||||
"block",
|
||||
},
|
||||
"c": {
|
||||
"function_definition",
|
||||
"struct_definition",
|
||||
"if_statement",
|
||||
"for_statement",
|
||||
"while_statement",
|
||||
"switch_statement",
|
||||
},
|
||||
"cpp": {
|
||||
"function_definition",
|
||||
"class_definition",
|
||||
"struct_definition",
|
||||
"namespace_definition",
|
||||
"if_statement",
|
||||
"for_statement",
|
||||
"while_statement",
|
||||
"switch_statement",
|
||||
},
|
||||
"go": {
|
||||
"function_declaration",
|
||||
"type_declaration",
|
||||
"if_statement",
|
||||
"for_statement",
|
||||
"select_statement",
|
||||
"switch_statement",
|
||||
},
|
||||
}
|
||||
24
plugins/code/ui/code_fold/folding_actions.py
Normal file
24
plugins/code/ui/code_fold/folding_actions.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def collapse_range(view, fold):
|
||||
buffer = view.get_buffer()
|
||||
start = buffer.get_iter_at_line(fold["start_line"] + 1)
|
||||
end = buffer.get_iter_at_line(fold["end_line"] + 1)
|
||||
|
||||
buffer.apply_tag_by_name("invisible", start, end)
|
||||
|
||||
|
||||
def expand_range(view, fold):
|
||||
buffer = view.get_buffer()
|
||||
start = buffer.get_iter_at_line(fold["start_line"] + 1)
|
||||
end = buffer.get_iter_at_line(fold["end_line"] + 1)
|
||||
|
||||
buffer.remove_tag_by_name("invisible", start, end)
|
||||
|
||||
|
||||
32
plugins/code/ui/code_fold/folding_engine.py
Normal file
32
plugins/code/ui/code_fold/folding_engine.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .fold_types import FOLD_NODES
|
||||
|
||||
|
||||
|
||||
def visit_node(fold_types, node, ranges):
|
||||
if node.type in fold_types:
|
||||
start_line = node.start_point[0]
|
||||
end_line = node.end_point[0]
|
||||
|
||||
if end_line > start_line:
|
||||
ranges.append({
|
||||
"start_line": start_line,
|
||||
"end_line": end_line,
|
||||
"id": (start_line, end_line, node.type),
|
||||
})
|
||||
|
||||
for child in node.children:
|
||||
visit_node(fold_types, child, ranges)
|
||||
|
||||
def get_folding_ranges(lang_name: str, ast):
|
||||
root = ast.root_node
|
||||
fold_types = FOLD_NODES.get(lang_name, set())
|
||||
ranges = []
|
||||
|
||||
visit_node(fold_types, root, ranges)
|
||||
return ranges
|
||||
|
||||
90
plugins/code/ui/code_fold/gutter_renderer.py
Normal file
90
plugins/code/ui/code_fold/gutter_renderer.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# 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 .folding_actions import collapse_range, expand_range
|
||||
|
||||
|
||||
|
||||
def handle_collapse(view, fold):
|
||||
collapse_range(view, fold)
|
||||
|
||||
def handle_expand(view, fold):
|
||||
expand_range(view, fold)
|
||||
|
||||
def handle_block_toggle(collapsed, view, fold):
|
||||
if not collapsed:
|
||||
handle_collapse(view, fold)
|
||||
else:
|
||||
handle_expand(view, fold)
|
||||
|
||||
def is_fold_hidden(buffer, fold):
|
||||
iter_ = buffer.get_iter_at_line(fold["start_line"] + 1)
|
||||
tags = iter_.get_tags()
|
||||
return any(tag.get_property("invisible") for tag in tags)
|
||||
|
||||
|
||||
def on_query_data(renderer, start_iter, end_iter, state, view):
|
||||
line = start_iter.get_line()
|
||||
|
||||
if not line in view.fold_start_set:
|
||||
renderer.set_text("", -1)
|
||||
return
|
||||
|
||||
fold = next(
|
||||
(f for f in view.fold_starts if f["start_line"] == line), None
|
||||
)
|
||||
|
||||
collapsed = fold and is_fold_hidden(view.get_buffer(), fold)
|
||||
|
||||
renderer.set_text("▶" if collapsed else "▼", -1)
|
||||
|
||||
def on_click(view, event, renderer):
|
||||
if not event.button == 1: return False
|
||||
window = view.get_window(Gtk.TextWindowType.LEFT)
|
||||
if not event.window == window: return False
|
||||
|
||||
x, y = view.window_to_buffer_coords(
|
||||
Gtk.TextWindowType.LEFT, int(event.x), int(event.y)
|
||||
)
|
||||
|
||||
_, iter_ = view.get_iter_at_location(x, y)
|
||||
line = iter_.get_line()
|
||||
|
||||
if line not in view.fold_start_set: return False
|
||||
|
||||
for fold in view.fold_starts:
|
||||
if not fold["start_line"] == line: continue
|
||||
|
||||
collapsed = is_fold_hidden(view.get_buffer(), fold)
|
||||
handle_block_toggle(collapsed, view, fold)
|
||||
|
||||
renderer.queue_draw()
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def setup_gutter(view):
|
||||
gutter = view.get_gutter(Gtk.TextWindowType.LEFT)
|
||||
buffer = view.get_buffer()
|
||||
|
||||
view.fold_starts = []
|
||||
view.fold_start_set = set()
|
||||
|
||||
renderer = GtkSource.GutterRendererText()
|
||||
renderer.set_size(12)
|
||||
renderer.set_padding(2, -1)
|
||||
|
||||
renderer.query_data_id = renderer.connect("query-data", on_query_data, view)
|
||||
view.collapse_click_id = view.connect("button-press-event", on_click, renderer)
|
||||
|
||||
gutter.insert(renderer, 0)
|
||||
view.fold_renderer = renderer
|
||||
7
plugins/code/ui/code_fold/manifest.json
Normal file
7
plugins/code/ui/code_fold/manifest.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Code Fold",
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"support": "",
|
||||
"requests": {}
|
||||
}
|
||||
94
plugins/code/ui/code_fold/plugin.py
Normal file
94
plugins/code/ui/code_fold/plugin.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
from plugins.plugin_types import PluginCode
|
||||
|
||||
from .fold_types import FOLD_NODES
|
||||
from .folding_actions import collapse_range
|
||||
from .folding_engine import get_folding_ranges
|
||||
from .gutter_renderer import setup_gutter
|
||||
|
||||
|
||||
|
||||
class Plugin(PluginCode):
|
||||
def _controller_message(self, event):
|
||||
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 not in FOLD_NODES:
|
||||
self.view.fold_start_set = {}
|
||||
return
|
||||
if not hasattr(file, "ast"):
|
||||
self.view.fold_start_set = {}
|
||||
return
|
||||
|
||||
buffer = file.buffer
|
||||
if not buffer.get_tag_table().lookup("invisible"):
|
||||
tag = buffer.create_tag("invisible")
|
||||
tag.set_property("invisible", True)
|
||||
|
||||
self.update_gutter(file, self.view)
|
||||
elif isinstance(event, Code_Event_Types.TextChangedEvent):
|
||||
if event.file.ftype not in FOLD_NODES: return
|
||||
if not hasattr(event.file, "ast"): return
|
||||
|
||||
self.schedule_update(event.file, self.view)
|
||||
|
||||
def load(self):
|
||||
event = Event_Factory.create_event("get_source_views")
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
for view in event.response:
|
||||
setup_gutter(view)
|
||||
|
||||
def unload(self):
|
||||
event = Event_Factory.create_event("get_source_views")
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
for view in event.response:
|
||||
view.fold_renderer.disconnect(view.fold_renderer.query_data_id)
|
||||
view.disconnect(view.collapse_click_id)
|
||||
|
||||
gutter = view.get_gutter(Gtk.TextWindowType.LEFT)
|
||||
gutter.remove(view.fold_renderer)
|
||||
|
||||
def run(self):
|
||||
...
|
||||
|
||||
def schedule_update(self, file, view, delay=250):
|
||||
if hasattr(view, "fold_update_source") and view.fold_update_source:
|
||||
GLib.source_remove(view.fold_update_source)
|
||||
|
||||
def callback():
|
||||
self.update_gutter(file, view)
|
||||
|
||||
if hasattr(view, "fold_renderer"):
|
||||
view.fold_renderer.queue_draw()
|
||||
|
||||
view.fold_update_source = None
|
||||
return False
|
||||
|
||||
view.fold_update_source = GLib.timeout_add(delay, callback)
|
||||
|
||||
def update_gutter(self, file, view):
|
||||
view.fold_starts = get_folding_ranges(file.ftype, file.ast)
|
||||
view.fold_start_set = {
|
||||
fold["start_line"] for fold in view.fold_starts
|
||||
}
|
||||
@@ -30,6 +30,7 @@ class Plugin(PluginCode):
|
||||
|
||||
event = Event_Factory.create_event("get_active_view")
|
||||
self.emit_to("source_views", event)
|
||||
if not event.response: return
|
||||
code_minimap.set_smini_view(event.response)
|
||||
|
||||
def unload(self):
|
||||
|
||||
@@ -99,7 +99,7 @@ class MarkdownPreview(Gtk.Popover, MarkdownPreviewMixin):
|
||||
def _tggle_preview_updates(self, widget):
|
||||
self.is_preview_paused = not self.is_preview_paused
|
||||
|
||||
def _handle_destroy(self):
|
||||
def _handle_destroy(self, widget):
|
||||
self.disconnect_by_func(self._handle_hide)
|
||||
self.disconnect_by_func(self._handle_show)
|
||||
self.disconnect_by_func(self._handle_destroy)
|
||||
|
||||
@@ -41,18 +41,14 @@ class Plugin(PluginCode):
|
||||
def load(self):
|
||||
separator_right = self.request_ui_element("separator-right")
|
||||
markdown_preview.set_relative_to(separator_right)
|
||||
|
||||
event = Event_Factory.create_event("register_command",
|
||||
command_name = "tggle_markdown_preview",
|
||||
command = Handler,
|
||||
binding_mode = "released",
|
||||
binding = "<Shift><Control>m"
|
||||
)
|
||||
|
||||
self.emit_to("source_views", event)
|
||||
self._manage_signals("register_command")
|
||||
|
||||
def unload(self):
|
||||
event = Event_Factory.create_event("unregister_command",
|
||||
self._manage_signals("unregister_command")
|
||||
markdown_preview.destroy()
|
||||
|
||||
def _manage_signals(self, action: str):
|
||||
event = Event_Factory.create_event(action,
|
||||
command_name = "tggle_markdown_preview",
|
||||
command = Handler,
|
||||
binding_mode = "released",
|
||||
@@ -61,8 +57,6 @@ class Plugin(PluginCode):
|
||||
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
markdown_preview.destroy()
|
||||
|
||||
def run(self):
|
||||
...
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
@@ -25,8 +28,19 @@ class Plugin(PluginCode):
|
||||
|
||||
self.register_controller("tabs", self.tabs_controller)
|
||||
|
||||
code_container.add( self.tabs_controller.tabs_widget )
|
||||
code_container.reorder_child(self.tabs_controller.tabs_widget, 0)
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
viewport = Gtk.Viewport()
|
||||
|
||||
scrolled_win.set_overlay_scrolling(False)
|
||||
scrolled_win.set_size_request(-1, 50)
|
||||
|
||||
viewport.add( self.tabs_controller.tabs_widget )
|
||||
scrolled_win.add( viewport )
|
||||
code_container.add( scrolled_win )
|
||||
code_container.reorder_child(scrolled_win, 0)
|
||||
|
||||
viewport.show()
|
||||
scrolled_win.show()
|
||||
|
||||
event = Event_Factory.create_event("get_files")
|
||||
self.emit_to("files", event)
|
||||
@@ -36,7 +50,14 @@ class Plugin(PluginCode):
|
||||
def unload(self):
|
||||
self.unregister_controller("tabs")
|
||||
self.tabs_controller.unload_tabs()
|
||||
self.tabs_controller.tabs_widget.destroy()
|
||||
|
||||
tabs_widget = self.tabs_controller.tabs_widget
|
||||
viewport = tabs_widget.get_parent()
|
||||
scrolled_win = viewport.get_parent()
|
||||
|
||||
tabs_widget.destroy()
|
||||
viewport.destroy()
|
||||
scrolled_win.destroy()
|
||||
|
||||
self.tabs_controller.tabs_widget = None
|
||||
self.tabs_controller = None
|
||||
|
||||
@@ -27,7 +27,7 @@ class TabsWidget(Gtk.Notebook):
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_scrollable(True)
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("page-added", self._page_added)
|
||||
@@ -67,6 +67,7 @@ class TabsWidget(Gtk.Notebook):
|
||||
)
|
||||
|
||||
self.emit(event)
|
||||
self._scroll_to_center(tab)
|
||||
|
||||
def _bind_tab_menu(self, tab, page_widget):
|
||||
def do_context_menu(tab, eve, page_widget):
|
||||
@@ -81,6 +82,21 @@ class TabsWidget(Gtk.Notebook):
|
||||
page_widget
|
||||
)
|
||||
|
||||
def _scroll_to_center(self, tab):
|
||||
scrolled_win = self.get_parent().get_parent()
|
||||
alloc = tab.get_allocation()
|
||||
tab_x = alloc.x
|
||||
tab_width = alloc.width
|
||||
view_width = scrolled_win.get_allocated_width()
|
||||
target = tab_x + tab_width / 2 - view_width / 2
|
||||
adj = scrolled_win.get_hadjustment()
|
||||
lower = adj.get_lower()
|
||||
upper = adj.get_upper()
|
||||
page_size = adj.get_page_size()
|
||||
target = max(lower, min(target, upper - page_size))
|
||||
|
||||
adj.set_value(target)
|
||||
|
||||
def create_menu(self, page_widget) -> Gtk.Menu:
|
||||
context_menu = Gtk.Menu()
|
||||
close_submenu = Gtk.Menu()
|
||||
@@ -130,6 +146,7 @@ class TabsWidget(Gtk.Notebook):
|
||||
self.page_num(page_widget)
|
||||
)
|
||||
self.handler_unblock(self.switch_page_id)
|
||||
self._scroll_to_center(tab)
|
||||
|
||||
break
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ class ListBox(Gtk.ListBox):
|
||||
|
||||
def _row_activated(self, list_box, row = None):
|
||||
row = self.get_selected_row()
|
||||
if not row: return
|
||||
file = row.get_children()[0].file
|
||||
|
||||
event = Event_Factory.create_event(
|
||||
@@ -70,25 +71,63 @@ class ListBox(Gtk.ListBox):
|
||||
|
||||
def search_changed(self, entry):
|
||||
self.search_buffer_names(entry)
|
||||
|
||||
for row in self.get_children():
|
||||
if not row.is_visible(): continue
|
||||
self.select_row(row)
|
||||
break
|
||||
|
||||
def fuzzy_score(self, query, text):
|
||||
query = query.lower()
|
||||
text = text.lower()
|
||||
score = 0
|
||||
q_idx = 0
|
||||
|
||||
for char in text:
|
||||
if q_idx < len(query) and char == query[q_idx]:
|
||||
score += 1
|
||||
q_idx += 1
|
||||
|
||||
return score if q_idx == len(query) else 0
|
||||
|
||||
|
||||
def search_buffer_names(self, entry):
|
||||
text = entry.get_text()
|
||||
if not text:
|
||||
for row in self.get_children():
|
||||
row.show()
|
||||
|
||||
return
|
||||
query = entry.get_text().lower()
|
||||
rows = []
|
||||
|
||||
for row in self.get_children():
|
||||
child = row.get_children()[0]
|
||||
label_text = child.get_label()
|
||||
score = self.fuzzy_score(query, label_text) if query else 1
|
||||
child.label_text = label_text if not hasattr(child, "label_text") else child.label_text
|
||||
|
||||
row.show() \
|
||||
if text in child.get_label() else \
|
||||
rows.append((score, row, child, label_text))
|
||||
|
||||
rows.sort(key = lambda x: x[0], reverse = True)
|
||||
for score, row, child, label_text in rows:
|
||||
if query and score > 0:
|
||||
highlighted = self.highlight_match(label_text, query)
|
||||
child.set_markup(highlighted)
|
||||
row.show()
|
||||
elif not query:
|
||||
child.set_label(child.label_text)
|
||||
row.show()
|
||||
else:
|
||||
row.hide()
|
||||
|
||||
def highlight_match(self, text, query):
|
||||
i = 0
|
||||
result = ""
|
||||
|
||||
for char in text:
|
||||
if i < len(query) and char.lower() == query[i].lower():
|
||||
result += f"<b><i><u>{char}</u></i></b>"
|
||||
i += 1
|
||||
else:
|
||||
result += char
|
||||
|
||||
return result
|
||||
|
||||
def activate_row(self):
|
||||
self._row_activated(self)
|
||||
|
||||
@@ -97,29 +136,43 @@ class ListBox(Gtk.ListBox):
|
||||
|
||||
def move_row_selection_up(self):
|
||||
row = self.get_selected_row()
|
||||
next_row = self.get_row_at_index(row.get_index() - 1)
|
||||
if not row: return
|
||||
|
||||
if not next_row:
|
||||
next_row = self.get_row_at_index(
|
||||
len( self.get_children() ) - 1
|
||||
)
|
||||
rows = [r for r in self.get_children() if r.is_visible()]
|
||||
if not rows: return
|
||||
|
||||
self.select_row(next_row)
|
||||
try:
|
||||
idx = rows.index(row)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
next_idx = (idx - 1) % len(rows)
|
||||
self.select_row(rows[next_idx])
|
||||
|
||||
def move_row_selection_down(self):
|
||||
row = self.get_selected_row()
|
||||
next_row = self.get_row_at_index(row.get_index() + 1)
|
||||
if not row: return
|
||||
|
||||
if not next_row:
|
||||
next_row = self.get_row_at_index(0)
|
||||
rows = [r for r in self.get_children() if r.is_visible()]
|
||||
if not rows: return
|
||||
|
||||
self.select_row(next_row)
|
||||
try:
|
||||
idx = rows.index(row)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
next_idx = (idx + 1) % len(rows)
|
||||
self.select_row(rows[next_idx])
|
||||
|
||||
def add_row(self, file):
|
||||
row = Gtk.ListBoxRow()
|
||||
label = Gtk.Label(label = file.fname)
|
||||
label.file = file
|
||||
label.show()
|
||||
self.add(label)
|
||||
|
||||
row.add(label)
|
||||
row.show_all()
|
||||
|
||||
self.add(row)
|
||||
|
||||
def remove_row(self, event):
|
||||
for row in self.get_children():
|
||||
|
||||
@@ -52,8 +52,8 @@ class Plugin(PluginCode):
|
||||
)
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
source_view = event.response
|
||||
telescope.set_source_view(source_view)
|
||||
scrolled_win, source_view = event.response
|
||||
telescope.set_source_view(scrolled_win, source_view)
|
||||
|
||||
event = Event_Factory.create_event(
|
||||
"register_completer",
|
||||
|
||||
@@ -99,13 +99,9 @@ class Telescope(Gtk.Dialog):
|
||||
def unmap_parent_resize_event(self, parent):
|
||||
parent.disconnect(self.size_allocate_id)
|
||||
|
||||
def set_source_view(self, source_view):
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
def set_source_view(self, scrolled_win, source_view):
|
||||
self.source_view = source_view
|
||||
|
||||
scrolled_win.add(self.source_view)
|
||||
self.main_box.pack_end(scrolled_win, True, True, 0)
|
||||
|
||||
scrolled_win.show_all()
|
||||
|
||||
def _handle_destroy(self, widget):
|
||||
|
||||
3
plugins/code/ui/terminals/__init__.py
Normal file
3
plugins/code/ui/terminals/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Module
|
||||
"""
|
||||
3
plugins/code/ui/terminals/__main__.py
Normal file
3
plugins/code/ui/terminals/__main__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Package
|
||||
"""
|
||||
7
plugins/code/ui/terminals/manifest.json
Normal file
7
plugins/code/ui/terminals/manifest.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Terminals",
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"support": "",
|
||||
"requests": {}
|
||||
}
|
||||
61
plugins/code/ui/terminals/plugin.py
Normal file
61
plugins/code/ui/terminals/plugin.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
from plugins.plugin_types import PluginCode
|
||||
|
||||
from .terminals_view import TerminalsView
|
||||
|
||||
|
||||
|
||||
terminals_view = TerminalsView()
|
||||
|
||||
|
||||
|
||||
class Plugin(PluginCode):
|
||||
def __init__(self):
|
||||
super(Plugin, self).__init__()
|
||||
|
||||
|
||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||
...
|
||||
|
||||
def load(self):
|
||||
terminals_view.emit_to = self.emit_to
|
||||
footer = self.request_ui_element("footer-container")
|
||||
footer.add( terminals_view )
|
||||
|
||||
self._manage_signals("register_command")
|
||||
|
||||
def unload(self):
|
||||
self._manage_signals("unregister_command")
|
||||
terminals_view.destroy()
|
||||
|
||||
def _manage_signals(self, action: str):
|
||||
event = Event_Factory.create_event(action,
|
||||
command_name = "terminals",
|
||||
command = Handler,
|
||||
binding_mode = "released",
|
||||
binding = "<Control>."
|
||||
)
|
||||
|
||||
self.emit_to("source_views", event)
|
||||
|
||||
def run(self):
|
||||
...
|
||||
|
||||
|
||||
class Handler:
|
||||
@staticmethod
|
||||
def execute(
|
||||
view: any,
|
||||
*args,
|
||||
**kwargs
|
||||
):
|
||||
logger.debug("Command: Terminal")
|
||||
terminals_view.set_code_view(view)
|
||||
|
||||
terminals_view.hide() if terminals_view.is_visible() else terminals_view.show()
|
||||
206
plugins/code/ui/terminals/terminals_view.py
Normal file
206
plugins/code/ui/terminals/terminals_view.py
Normal file
@@ -0,0 +1,206 @@
|
||||
# Python imports
|
||||
import os
|
||||
import shlex
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gio', '2.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
gi.require_version('Gtk', '3.0')
|
||||
|
||||
from gi.repository import Gio
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Pango
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
from .vte_widget import VteWidget
|
||||
|
||||
|
||||
|
||||
class TerminalsView(Gtk.Notebook):
|
||||
def __init__(self):
|
||||
super(TerminalsView, self).__init__()
|
||||
|
||||
self.MARKERS: list = ["src", ".git", ".gitignore", "README.md"]
|
||||
self.code_view = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
self.hide()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("terminals-view")
|
||||
|
||||
self.set_scrollable(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("show", self._handle_show)
|
||||
self.connect("hide", self._handle_hide)
|
||||
self.connect("destroy", self._handle_destroy)
|
||||
|
||||
def _load_widgets(self):
|
||||
hbox = Gtk.Box()
|
||||
self.add_bttn = Gtk.Button(label = "✛")
|
||||
self.hide_bttn = Gtk.Button(label = "-")
|
||||
self.add_bttn.connect("clicked", self._create_terminal)
|
||||
self.hide_bttn.connect("clicked", self._hide_view)
|
||||
|
||||
hbox.add(self.add_bttn)
|
||||
hbox.add(self.hide_bttn)
|
||||
self.set_action_widget(hbox, Gtk.PackType.END)
|
||||
|
||||
self.create_terminal()
|
||||
hbox.show_all()
|
||||
|
||||
def _generate_terminal_parts(self):
|
||||
label = Gtk.Label(label = "...")
|
||||
vte_widget = VteWidget()
|
||||
|
||||
vte_widget.hide_view = self.hide
|
||||
vte_widget.go_to_project_or_home = self.go_to_project_or_home
|
||||
vte_widget.create_terminal = self.create_terminal
|
||||
vte_widget.close_terminal = self.close_terminal
|
||||
vte_widget.prev_terminal = self.prev_terminal
|
||||
vte_widget.next_terminal = self.next_terminal
|
||||
|
||||
label.set_text( vte_widget.get_home_path() )
|
||||
label.set_tooltip_text( vte_widget.get_home_path() )
|
||||
label.set_ellipsize(Pango.EllipsizeMode.START)
|
||||
label.set_single_line_mode(True)
|
||||
label.set_max_width_chars(32)
|
||||
label.set_size_request(240, -1)
|
||||
|
||||
vte_widget.bind_label(label)
|
||||
|
||||
return label, vte_widget
|
||||
|
||||
def _handle_show(self, widget):
|
||||
i = widget.get_current_page()
|
||||
term = widget.get_nth_page(i)
|
||||
|
||||
GLib.idle_add(term.grab_focus)
|
||||
|
||||
def _handle_hide(self, widget):
|
||||
if not self.code_view: return
|
||||
GLib.idle_add(self.code_view.grab_focus)
|
||||
|
||||
def _hide_view(self, widget):
|
||||
self.hide()
|
||||
|
||||
def _handle_destroy(self, widget):
|
||||
widget.disconnect_by_func(widget._handle_show)
|
||||
widget.disconnect_by_func(widget._handle_hide)
|
||||
widget.disconnect_by_func(widget._handle_destroy)
|
||||
self.add_bttn.disconnect_by_func(self._create_terminal)
|
||||
self.hide_bttn.disconnect_by_func(self._hide_view)
|
||||
|
||||
def _create_terminal(self, widget):
|
||||
self.create_terminal()
|
||||
|
||||
def has_marker(self, gfile):
|
||||
try:
|
||||
enumerator = gfile.enumerate_children(
|
||||
"standard::name,standard::type",
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
None
|
||||
)
|
||||
|
||||
while True:
|
||||
info = enumerator.next_file(None)
|
||||
if info is None:
|
||||
break
|
||||
|
||||
if info.get_name() in self.MARKERS:
|
||||
enumerator.close(None)
|
||||
return True
|
||||
|
||||
enumerator.close(None)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
def find_project_path_or_home(self, current: Gio.File):
|
||||
if not current: return
|
||||
|
||||
home = Gio.File.new_for_path( os.path.expanduser("~") )
|
||||
while True:
|
||||
if self.has_marker(current):
|
||||
return current.get_path()
|
||||
|
||||
if current.equal(home):
|
||||
return current.get_path()
|
||||
|
||||
parent = current.get_parent()
|
||||
if parent is None:
|
||||
return current.get_path()
|
||||
|
||||
current = parent
|
||||
|
||||
def set_code_view(self, widget):
|
||||
self.code_view = widget
|
||||
|
||||
def go_to_project_or_home(self):
|
||||
event = Event_Factory.create_event("get_file",
|
||||
buffer = self.code_view.get_buffer()
|
||||
)
|
||||
|
||||
self.emit_to("files", event)
|
||||
|
||||
if event.response.ftype == "buffer": return
|
||||
|
||||
gfile = event.response.get_location().get_parent()
|
||||
fpath = self.find_project_path_or_home(gfile)
|
||||
i = self.get_current_page()
|
||||
widget = self.get_nth_page(i)
|
||||
|
||||
widget.run_command(f"cd {shlex.quote(fpath)} && clear\n")
|
||||
|
||||
def create_terminal(self):
|
||||
label, vte_widget = self._generate_terminal_parts()
|
||||
index = self.append_page(vte_widget, label)
|
||||
self.set_tab_detachable(vte_widget, True)
|
||||
self.set_tab_reorderable(vte_widget, True)
|
||||
|
||||
self.set_current_page(index)
|
||||
GLib.idle_add(vte_widget.grab_focus)
|
||||
|
||||
self.show_all()
|
||||
|
||||
def close_terminal(self):
|
||||
size = self.get_n_pages()
|
||||
if size == 1: return
|
||||
|
||||
i = self.get_current_page()
|
||||
widget = self.get_nth_page(i)
|
||||
self.remove_page(i)
|
||||
widget.destroy()
|
||||
|
||||
def prev_terminal(self):
|
||||
i = self.get_current_page() - 1
|
||||
size = self.get_n_pages()
|
||||
|
||||
if i < 0:
|
||||
self.set_current_page(size - 1)
|
||||
return
|
||||
|
||||
self.prev_page()
|
||||
|
||||
def next_terminal(self):
|
||||
i = self.get_current_page() + 1
|
||||
size = self.get_n_pages()
|
||||
|
||||
if i == size:
|
||||
self.set_current_page(0)
|
||||
return
|
||||
|
||||
self.next_page()
|
||||
180
plugins/code/ui/terminals/vte_widget.py
Normal file
180
plugins/code/ui/terminals/vte_widget.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# Python imports
|
||||
import os
|
||||
from os import path
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
gi.require_version('Vte', '2.91')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Vte
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class VteWidgetException(Exception):
|
||||
...
|
||||
|
||||
|
||||
|
||||
class VteWidget(Vte.Terminal):
|
||||
"""
|
||||
https://stackoverflow.com/questions/60454326/how-to-implement-a-linux-terminal-in-a-pygtk-app-like-vscode-and-pycharm-has
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(VteWidget, self).__init__()
|
||||
|
||||
self._USER_HOME: str = path.expanduser('~')
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
self._do_session_spawn()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("vte-widget")
|
||||
|
||||
self.set_clear_background(False)
|
||||
self.set_hexpand(True)
|
||||
self.set_enable_sixel(True)
|
||||
self.set_cursor_shape( Vte.CursorShape.IBEAM )
|
||||
self.set_audible_bell(False)
|
||||
self.set_scroll_on_output(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("commit", self._handle_commit)
|
||||
self.connect("current-directory-uri-changed", self._handle_path_change)
|
||||
self.connect("selection-changed", self._handle_selection)
|
||||
self.connect("button-press-event", self._on_button_press)
|
||||
self.connect("key-press-event", self._on_key_press)
|
||||
self.connect("key-release-event", self._on_key_release)
|
||||
self.connect("destroy", self._handle_destroy)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def _do_session_spawn(self):
|
||||
env_dict = os.environ.copy()
|
||||
existing_pc = env_dict.get("PROMPT_COMMAND", "")
|
||||
# Note: Needed for 'current-directory-uri-changed' to work.
|
||||
# Make sure user .bashrc doesn't affect it...
|
||||
osc7 = 'printf "\\033]7;file://%s%s\\007" "$PWD"'
|
||||
|
||||
env_dict.update({
|
||||
"LC_ALL": "C",
|
||||
"TERM": "xterm-256color",
|
||||
"HISTFILE": "/dev/null",
|
||||
"HISTSIZE": "0",
|
||||
"HISTFILESIZE": "0",
|
||||
"PS1": "\\h@\\u \\W -->: ",
|
||||
"PROMPT_COMMAND": f"{osc7};{existing_pc}" if existing_pc else osc7,
|
||||
})
|
||||
|
||||
env = [f"{k}={v}" for k, v in env_dict.items()]
|
||||
|
||||
self.spawn_async(
|
||||
Vte.PtyFlags.DEFAULT,
|
||||
self._USER_HOME,
|
||||
["/bin/bash"],
|
||||
env,
|
||||
GLib.SpawnFlags.DEFAULT,
|
||||
None, None, -1, None, None,
|
||||
)
|
||||
|
||||
self.set_scrollback_lines(15000)
|
||||
|
||||
def _handle_destroy(self, terminal):
|
||||
logger.debug("Destroying terminal...")
|
||||
terminal.disconnect_by_func(terminal._handle_commit)
|
||||
terminal.disconnect_by_func(terminal._handle_path_change)
|
||||
terminal.disconnect_by_func(terminal._handle_selection)
|
||||
terminal.disconnect_by_func(terminal._on_button_press)
|
||||
terminal.disconnect_by_func(terminal._on_key_press)
|
||||
terminal.disconnect_by_func(terminal._on_key_release)
|
||||
terminal.disconnect_by_func(terminal._handle_destroy)
|
||||
|
||||
def _handle_commit(self, terminal, text, size):
|
||||
...
|
||||
|
||||
def _handle_selection(self, *args):
|
||||
if self.get_has_selection():
|
||||
self.copy_primary()
|
||||
|
||||
def _on_button_press(self, widget, event):
|
||||
if event.button == 2: # middle click
|
||||
self.paste_clipboard()
|
||||
return True
|
||||
|
||||
def _on_key_press(self, widget, event):
|
||||
ctrl_pressed = event.state & Gdk.ModifierType.CONTROL_MASK
|
||||
shift_pressed = event.state & Gdk.ModifierType.SHIFT_MASK
|
||||
|
||||
if event.keyval == Gdk.KEY_Home:
|
||||
self.go_to_project_or_home()
|
||||
return True
|
||||
|
||||
if ctrl_pressed:
|
||||
if shift_pressed:
|
||||
if event.keyval in [Gdk.KEY_C, Gdk.KEY_V]:
|
||||
if event.keyval == Gdk.KEY_C:
|
||||
self.copy_clipboard()
|
||||
elif event.keyval == Gdk.KEY_V:
|
||||
self.paste_clipboard()
|
||||
|
||||
return True
|
||||
|
||||
if event.keyval in [
|
||||
Gdk.KEY_period, Gdk.KEY_t, Gdk.KEY_w, Gdk.KEY_Up, Gdk.KEY_Down
|
||||
]:
|
||||
if event.keyval == Gdk.KEY_period:
|
||||
if hasattr(self, "hide_view"):
|
||||
GLib.timeout_add(200, self.hide_view)
|
||||
elif event.keyval == Gdk.KEY_t:
|
||||
if hasattr(self, "create_terminal"):
|
||||
self.create_terminal()
|
||||
elif event.keyval == Gdk.KEY_w:
|
||||
if hasattr(self, "close_terminal"):
|
||||
self.close_terminal()
|
||||
elif event.keyval == Gdk.KEY_Up:
|
||||
if hasattr(self, "prev_terminal"):
|
||||
self.prev_terminal()
|
||||
elif event.keyval == Gdk.KEY_Down:
|
||||
if hasattr(self, "next_terminal"):
|
||||
self.next_terminal()
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _on_key_release(self, widget, event):
|
||||
...
|
||||
|
||||
def _handle_path_change(self, terminal):
|
||||
if not hasattr(self, "label"): return
|
||||
|
||||
uri = terminal.get_current_directory_uri().replace("file://", "")
|
||||
|
||||
terminal.label.set_text(uri)
|
||||
terminal.label.set_tooltip_text(uri)
|
||||
|
||||
def get_home_path(self):
|
||||
return self._USER_HOME
|
||||
|
||||
def bind_label(self, label: Gtk.Label):
|
||||
self.label = label
|
||||
|
||||
def run_command(self, cmd: str):
|
||||
self.feed_child_binary(bytes(cmd, 'utf8'))
|
||||
@@ -6,9 +6,10 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from core.widgets.save_file_dialog import SaveFileDialog
|
||||
from core.widgets.controls.open_files_button import OpenFilesButton
|
||||
|
||||
from .code.code_container import CodeContainer
|
||||
from ..widgets.save_file_dialog import SaveFileDialog
|
||||
from ..widgets.controls.open_files_button import OpenFilesButton
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,10 +6,6 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ...widgets.code.code_base import CodeBase
|
||||
|
||||
from ...widgets.separator_widget import Separator
|
||||
|
||||
from .editors_container import EditorsContainer
|
||||
|
||||
|
||||
@@ -27,6 +23,9 @@ class CodeContainer(Gtk.Box):
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("code-container")
|
||||
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
def _setup_signals(self):
|
||||
@@ -37,18 +36,4 @@ class CodeContainer(Gtk.Box):
|
||||
|
||||
def _load_widgets(self):
|
||||
widget_registery.expose_object("code-container", self)
|
||||
|
||||
code_base = CodeBase()
|
||||
|
||||
self.add( self._create_editor_widget(code_base) )
|
||||
|
||||
def _create_editor_widget(self, code_base: CodeBase):
|
||||
editors_container = Gtk.Box()
|
||||
|
||||
widget_registery.expose_object("editors-container", editors_container)
|
||||
|
||||
editors_container.add( Separator("separator_left") )
|
||||
editors_container.add( EditorsContainer(code_base) )
|
||||
editors_container.add( Separator("separator_right") )
|
||||
|
||||
return editors_container
|
||||
self.add( EditorsContainer() )
|
||||
|
||||
@@ -4,18 +4,17 @@
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
#from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from core.widgets.code.code_base import CodeBase
|
||||
from core.widgets.separator_widget import Separator
|
||||
|
||||
|
||||
|
||||
class EditorsContainer(Gtk.Paned):
|
||||
def __init__(self, code_base: any):
|
||||
class EditorsContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(EditorsContainer, self).__init__()
|
||||
|
||||
self.code_base = code_base
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
@@ -24,11 +23,11 @@ class EditorsContainer(Gtk.Paned):
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("paned-editors-container")
|
||||
self.ctx.add_class("editors-container")
|
||||
|
||||
self.set_hexpand(True)
|
||||
self.set_vexpand(True)
|
||||
self.set_wide_handle(True)
|
||||
self.set_size_request(320, -1)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("map", self._init_map)
|
||||
@@ -37,29 +36,18 @@ class EditorsContainer(Gtk.Paned):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
self.scrolled_win1, \
|
||||
self.scrolled_win2 = self._create_views()
|
||||
box = Gtk.Box()
|
||||
widget_registery.expose_object("editors-container", self)
|
||||
self.code_base = CodeBase()
|
||||
scrolled_win, \
|
||||
source_view = self.code_base.create_source_view()
|
||||
|
||||
self.pack1( self.scrolled_win1, True, True )
|
||||
self.pack2( self.scrolled_win2, True, True )
|
||||
|
||||
def _create_views(self):
|
||||
scrolled_win1 = Gtk.ScrolledWindow()
|
||||
scrolled_win2 = Gtk.ScrolledWindow()
|
||||
|
||||
source_view1 = self.code_base.create_source_view()
|
||||
source_view2 = self.code_base.create_source_view()
|
||||
|
||||
source_view1.sibling_right = source_view2
|
||||
source_view2.sibling_left = source_view1
|
||||
|
||||
scrolled_win1.add( source_view1 )
|
||||
scrolled_win2.add( source_view2 )
|
||||
|
||||
return scrolled_win1, scrolled_win2
|
||||
box.add( scrolled_win )
|
||||
self.add( Separator("separator_left") )
|
||||
self.add( box )
|
||||
self.add( Separator("separator_right") )
|
||||
|
||||
def _init_map(self, view):
|
||||
self.disconnect_by_func( self._init_map )
|
||||
self.code_base.first_map_load()
|
||||
self.code_base = None
|
||||
del self.code_base
|
||||
|
||||
@@ -6,8 +6,8 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.separator_widget import Separator
|
||||
from ..widgets.vte_widget import VteWidget
|
||||
from core.widgets.separator_widget import Separator
|
||||
from core.widgets.vte_widget import VteWidget
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.separator_widget import Separator
|
||||
from core.widgets.separator_widget import Separator
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.separator_widget import Separator
|
||||
from core.widgets.separator_widget import Separator
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.separator_widget import Separator
|
||||
from core.widgets.separator_widget import Separator
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -18,16 +18,12 @@ class BaseControllerMixin:
|
||||
files = []
|
||||
|
||||
for arg in unknownargs + [args.new_tab,]:
|
||||
if os.path.isdir( arg.replace("file://", "") ):
|
||||
files.append( f"DIR|{arg.replace('file://', '')}" )
|
||||
continue
|
||||
if os.path.isfile(arg):
|
||||
files.append(f"{arg}")
|
||||
|
||||
# NOTE: If passing line number with file split against :
|
||||
if os.path.isfile( arg.replace("file://", "").split(":")[0] ):
|
||||
files.append( f"FILE|{arg.replace('file://', '')}" )
|
||||
continue
|
||||
|
||||
logger.info(f"Not a File: {arg}")
|
||||
if os.path.isdir(arg):
|
||||
message = f"DIR|{arg}"
|
||||
ipc_server.send_ipc_message(message)
|
||||
|
||||
if not files: return
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user