feat(tree-sitter, views): initialize AST on focus and emit source view creation event
- Add set_ast helper to centralize Tree-sitter parsing logic - Parse and attach AST on FocusedViewEvent and TextChangedEvent - Request file from buffer on view focus before parsing - Fix parser guard condition in get_parser (handle missing language properly) - Emit CreatedSourceViewEvent when a new source view is added - Register CreatedSourceViewEvent in DTO exports - Update TODO: - Remove completed collapsible code blocks task - Add fix note for code block icon desync issue chore: - Add scaffolding for code_fold UI plugin - Add created_source_view_event DTO
This commit is contained in:
2
TODO.md
2
TODO.md
@@ -1,6 +1,5 @@
|
||||
___
|
||||
### Add
|
||||
1. Add Collapsable code blocks
|
||||
1. Add Godot LSP Client
|
||||
1. Add Terminal plugin
|
||||
1. Add Plugin to <Shift\><Ctrl\>| and <Ctrl\>| to split views up, down, left, right
|
||||
@@ -14,5 +13,6 @@ ___
|
||||
___
|
||||
### Fix
|
||||
- Fix on lsp client unload to close files lsp side and unload server endpoint
|
||||
- Fix Collapsable code blocks icon desync on new/old lines or text cut/pasted
|
||||
|
||||
___
|
||||
|
||||
@@ -16,21 +16,37 @@ class Plugin(PluginCode):
|
||||
super(Plugin, self).__init__()
|
||||
|
||||
|
||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||
if isinstance(event, Code_Event_Types.TextChangedEvent):
|
||||
if not hasattr(event.file, "tree_sitter"):
|
||||
parser = get_parser( event.file.ftype )
|
||||
def set_ast(self, file):
|
||||
if not hasattr(file, "tree_sitter"):
|
||||
parser = get_parser( file.ftype )
|
||||
if not parser: return
|
||||
|
||||
event.file.tree_sitter = parser
|
||||
file.tree_sitter = parser
|
||||
|
||||
buffer = event.file.buffer
|
||||
buffer = file.buffer
|
||||
start_itr, \
|
||||
end_itr = buffer.get_bounds()
|
||||
text = buffer.get_text(start_itr, end_itr, True)
|
||||
|
||||
tree = event.file.tree_sitter.parse( text.encode("UTF-8") )
|
||||
event.file.ast = tree
|
||||
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)
|
||||
|
||||
@@ -48,7 +48,7 @@ def get_parser(lang_name: str) -> Parser | None:
|
||||
|
||||
language = LANGUAGES[lang_name]
|
||||
|
||||
if not language in LANGUAGES: return
|
||||
if not language: return
|
||||
|
||||
parser = Parser()
|
||||
parser.language = language
|
||||
|
||||
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
|
||||
"""
|
||||
42
plugins/code/ui/code_fold/fold_types.py
Normal file
42
plugins/code/ui/code_fold/fold_types.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# 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",
|
||||
},
|
||||
"java": {
|
||||
"class_declaration",
|
||||
"method_declaration",
|
||||
"constructor_declaration",
|
||||
"if_statement",
|
||||
"for_statement",
|
||||
"while_statement",
|
||||
"switch_expression",
|
||||
"block",
|
||||
},
|
||||
"json": {
|
||||
"object",
|
||||
"array",
|
||||
},
|
||||
}
|
||||
22
plugins/code/ui/code_fold/folding_actions.py
Normal file
22
plugins/code/ui/code_fold/folding_actions.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# 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)
|
||||
31
plugins/code/ui/code_fold/folding_engine.py
Normal file
31
plugins/code/ui/code_fold/folding_engine.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .fold_types import FOLD_NODES
|
||||
|
||||
|
||||
|
||||
def get_folding_ranges(lang_name: str, ast):
|
||||
root = ast.root_node
|
||||
fold_types = FOLD_NODES.get(lang_name, set())
|
||||
ranges = []
|
||||
|
||||
def visit(node):
|
||||
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(child)
|
||||
|
||||
visit(root)
|
||||
return ranges
|
||||
93
plugins/code/ui/code_fold/gutter_renderer.py
Normal file
93
plugins/code/ui/code_fold/gutter_renderer.py
Normal file
@@ -0,0 +1,93 @@
|
||||
# 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)
|
||||
|
||||
for inner in view.fold_starts:
|
||||
if fold["start_line"] < inner["start_line"] <= fold["end_line"]:
|
||||
view.fold_states[inner["id"]] = False
|
||||
|
||||
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 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 view.fold_states.get(fold["id"], False)
|
||||
|
||||
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 = view.fold_states.get(fold["id"], False)
|
||||
view.fold_states[fold["id"]] = not collapsed
|
||||
|
||||
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()
|
||||
|
||||
if not buffer.get_tag_table().lookup("invisible"):
|
||||
tag = buffer.create_tag("invisible")
|
||||
tag.set_property("invisible", True)
|
||||
|
||||
view.fold_starts = []
|
||||
view.fold_start_set = set()
|
||||
view.fold_states = {}
|
||||
|
||||
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": {}
|
||||
}
|
||||
98
plugins/code/ui/code_fold/plugin.py
Normal file
98
plugins/code/ui/code_fold/plugin.py
Normal file
@@ -0,0 +1,98 @@
|
||||
# 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_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: return
|
||||
if not hasattr(file, "ast"): return
|
||||
|
||||
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):
|
||||
old_states = getattr(view, "fold_states", {})
|
||||
|
||||
view.fold_starts = get_folding_ranges(file.ftype, file.ast)
|
||||
view.fold_start_set = {
|
||||
fold["start_line"] for fold in view.fold_starts
|
||||
}
|
||||
|
||||
buffer = view.get_buffer()
|
||||
if not buffer.get_tag_table().lookup("invisible"):
|
||||
tag = buffer.create_tag("invisible")
|
||||
tag.set_property("invisible", True)
|
||||
|
||||
new_states = {}
|
||||
for fold in view.fold_starts:
|
||||
if not fold["id"] in old_states: continue
|
||||
new_states[fold["id"]] = old_states[fold["id"]]
|
||||
|
||||
view.fold_states = new_states
|
||||
@@ -111,6 +111,12 @@ class SourceViewsController(ControllerBase, list):
|
||||
self.signal_mapper.connect_signals(source_view)
|
||||
|
||||
self.append(source_view)
|
||||
|
||||
event = Event_Factory.create_event(
|
||||
"created_source_view", view = source_view
|
||||
)
|
||||
self.emit(event)
|
||||
|
||||
return source_view
|
||||
|
||||
def first_map_load(self):
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
from .code_event import CodeEvent
|
||||
from .toggle_plugins_ui_event import TogglePluginsUiEvent
|
||||
from .create_source_view_event import CreateSourceViewEvent
|
||||
from .created_source_view_event import CreatedSourceViewEvent
|
||||
from .register_completer_event import RegisterCompleterEvent
|
||||
from .unregister_completer_event import UnregisterCompleterEvent
|
||||
from .register_provider_event import RegisterProviderEvent
|
||||
|
||||
14
src/libs/dto/code/events/created_source_view_event.py
Normal file
14
src/libs/dto/code/events/created_source_view_event.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .code_event import CodeEvent
|
||||
from libs.dto.states.source_view_states import SourceViewStates
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class CreatedSourceViewEvent(CodeEvent):
|
||||
...
|
||||
Reference in New Issue
Block a user