Clean up codebase and improve file loading

- Moved plugins to proper sub groups (autopairs, code_minimap, colorize, commentzar, info_bar, markdown_preview, prettify_json, search_replace, tabs_bar, telescope, toggle_source_view, lsp_client)
- Add filter_out_loaded_files to prevent opening already-loaded files
- Add INDEPENDENT source view state
- Fix cursor scroll position on buffer switch
- Fix signal blocking during file load
- Fix word boundary in completion provider
- Refactor code events into single events module
This commit is contained in:
2026-03-08 00:51:28 -06:00
parent a52d5243ab
commit 99dc917de3
229 changed files with 8809 additions and 756 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,55 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .autopairs import Autopairs
autopairs = Autopairs()
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 = "autopairs",
command = Handler,
binding_mode = "held",
binding = [
"'", "`", "[", "]",
'<Shift>"',
'<Shift>(',
'<Shift>)',
'<Shift>{',
'<Shift>}'
]
)
self.emit_to("source_views", event)
def run(self):
...
class Handler:
@staticmethod
def execute(
view: any,
char_str: str,
*args,
**kwargs
):
logger.debug("Command: Autopairs")
autopairs.handle_word_wrap(view.get_buffer(), char_str)

View File

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

View File

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

View File

@@ -0,0 +1,111 @@
# Python imports
import colorsys
# Lib imports
# Application imports
class ColorConverterMixinException(Exception):
...
class ColorConverterMixin:
# NOTE: HSV HSL, and Hex Alpha parsing are available in Gtk 4.0- not lower.
# So, for compatability we're gunna convert to rgba string ourselves...
def get_color_text(self, buffer, start_itr, end_itr):
text = buffer.get_text(start_itr, end_itr, include_hidden_chars = False)
try:
if "hsl" in text:
text = self.hsl_to_rgb(text)
if "hsv" in text:
text = self.hsv_to_rgb(text)
if "#" == text[0]:
hex = text[1:]
size = len(hex)
if size in [4, 8, 16]:
rgba = self.hex_to_rgba(hex, size)
logger.debug(f"Colorize Plugin: RGBA = {rgba}")
except ColorConverterMixinException as e:
...
return text
def hex_to_rgba(self, hex: str, size: int) -> str:
rgba = []
slots = None
step = 2
bytes = 16
if size == 4: # NOTE: RGBA
step = 1
slots = (0, 1, 2, 3)
if size == 6: # NOTE: RR GG BB
slots = (0, 2, 4)
if size == 8: # NOTE: RR GG BB AA
step = 2
slots = (0, 2, 4, 6)
if size == 16: # NOTE: RRRR GGGG BBBB AAAA
step = 4
slots = (0, 4, 8, 12)
for i in slots:
v = int(hex[i : i + step], bytes)
rgba.append(v)
rgb_sub = ','.join(map(str, tuple(rgba)))
return f"rgba({rgb_sub})"
# return tuple(rgba)
def hsl_to_rgb(self, text: str) -> str:
_h, _s , _l = text.replace("hsl", "") \
.replace("deg", "") \
.replace("(", "") \
.replace(")", "") \
.replace("%", "") \
.replace(" ", "") \
.split(",")
h = None
s = None
l = None
h, s , l = int(_h) / 360, float(_s) / 100, float(_l) / 100
rgb = tuple(round(i * 255) for i in colorsys.hls_to_rgb(h, l, s))
rgb_sub = ','.join(map(str, rgb))
return f"rgb({rgb_sub})"
def hsv_to_rgb(self, text: str) -> str:
_h, _s , _v = text.replace("hsv", "") \
.replace("deg", "") \
.replace("(", "") \
.replace(")", "") \
.replace("%", "") \
.replace(" ", "") \
.split(",")
h = None
s = None
v = None
h, s , v = int(_h) / 360, float(_s) / 100, float(_v) / 100
rgb = tuple(round(i * 255) for i in colorsys.hsv_to_rgb(h,s,v))
rgb_sub = ','.join(map(str, rgb))
return f"rgb({rgb_sub})"

View File

@@ -0,0 +1,240 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
# Application imports
from .color_converter_mixin import ColorConverterMixin
class Colorize(ColorConverterMixin):
def __init__(self):
super(Colorize, self).__init__()
self.tag_stub_name: str = "colorize-tag"
self.is_colorize_paused: bool = True
def handle_colorize(self, buffer):
if self.is_colorize_paused: return
tag_table = buffer.get_tag_table()
start_itr = None
end_itr = buffer.get_iter_at_mark( buffer.get_insert() )
i = 0
walker_iter = end_itr.copy()
working_tag = self.find_working_tag(walker_iter, i)
if working_tag:
start_itr = self.find_start_range(walker_iter, working_tag)
self.find_end_range(end_itr, working_tag)
buffer.remove_tag(working_tag, start_itr, end_itr)
else:
start_itr = self.traverse_backward_25_or_less(walker_iter)
self.traverse_forward_25_or_less(end_itr)
self.do_colorize(buffer, start_itr, end_itr)
def do_colorize(self, buffer = None, start_itr = None, end_itr = None):
if not start_itr or not end_itr:
start_itr = buffer.get_start_iter()
end_itr = buffer.get_end_iter()
# rgb(a), hsl, hsv
results = self.finalize_non_hex_matches(
self.collect_preliminary_results(
buffer, start_itr, end_itr
)
)
self.process_results(buffer, results)
# hex color search
results = self.finalize_hex_matches(
self.collect_preliminary_hex_results(
buffer, start_itr, end_itr
)
)
self.process_results(buffer, results)
def collect_preliminary_results(self, buffer = None, start_itr = None, end_itr = None):
if not buffer: return []
if not start_itr:
start_itr = buffer.get_start_iter()
results1 = self.search(start_itr, end_itr, "rgb")
results2 = self.search(start_itr, end_itr, "hsl")
results3 = self.search(start_itr, end_itr, "hsv")
return results1 + results2 + results3
def find_working_tag(self, walker_iter, i):
tags = walker_iter.get_tags()
for tag in tags:
if not tag.props.name or not self.tag_stub_name in tag.props.name: continue
return tag
result = walker_iter.backward_char()
if not result: return
if i > 25: return
return self.find_working_tag(walker_iter, i + 1)
def find_start_range(self, walker_iter, working_tag):
tags = walker_iter.get_tags()
for tag in tags:
if not tag.props.name or not working_tag.props.name in tag.props.name: continue
if not walker_iter.backward_char(): continue
self.find_start_range(walker_iter, working_tag)
return walker_iter
def find_end_range(self, end_itr, working_tag):
tags = end_itr.get_tags()
for tag in tags:
if not tag.props.name or not working_tag.props.name in tag.props.name: continue
if not end_itr.forward_char(): continue
self.find_end_range(end_itr, working_tag)
def traverse_backward_25_or_less(self, walker_itr):
i = 1
while i <= 25:
res = walker_itr.backward_char()
if not res: break
i += 1
def traverse_forward_25_or_less(self, end_itr):
i = 1
while i <= 25:
res = end_itr.forward_char()
if not res: break
i += 1
def collect_preliminary_hex_results(
self,
buffer = None,
start_itr = None,
end_itr = None
) -> list:
if not buffer: return []
if not start_itr:
start_itr = buffer.get_start_iter()
return self.search(start_itr, end_itr, "#")
def search(self, start_itr = None, end_itr = None, query: str = None) -> list:
if not start_itr or not query: return []
results: list = []
flags = Gtk.TextSearchFlags.VISIBLE_ONLY | Gtk.TextSearchFlags.TEXT_ONLY
while True:
result = start_itr.forward_search(query, flags, end_itr)
if not result: break
results.append(result)
start_itr = result[1]
return results
def finalize_non_hex_matches(self, result_hits: list = []) -> list:
results: list = []
for start_itr, end_itr in result_hits:
# If one of end chars of rgb/rgba/hsv/hsl
if end_itr.get_char() in ["a", "b", "l", "v"]:
end_itr.forward_char()
# If afterwards no paren
if end_itr.get_char() != "(":
continue
end_itr.forward_chars(21) # Check if best case (255, 255, 255, 0.64)
if end_itr.get_char() == ")":
end_itr.forward_char()
results.append([start_itr, end_itr])
continue
# Break loop if we get back to rgb/rgba/hsl/hsv -> (
while end_itr.get_char() != "(":
if end_itr.get_char() == ")":
end_itr.forward_char()
results.append([start_itr, end_itr])
break
if not end_itr.backward_char(): break
return results
def finalize_hex_matches(self, result_hits: list = []) -> list:
results: list = []
for start_itr, end_itr in result_hits:
i = 0
_ch = end_itr.get_char()
ch = ord(end_itr.get_char()) if _ch else -1
while (
(ch >= 48 and ch <= 57) or \
(ch >= 65 and ch <= 70) or \
(ch >= 97 and ch <= 102)
):
if i > 16: break
i += 1
end_itr.forward_char()
_ch = end_itr.get_char()
ch = ord(end_itr.get_char()) if _ch else -1
if i in [3, 4, 6, 8, 9, 12, 16]:
results.append([start_itr, end_itr])
return results
def process_results(self, buffer, results):
for start_itr, end_itr in results:
text = self.get_color_text(buffer, start_itr, end_itr)
color = Gdk.RGBA()
if not color.parse(text): continue
tag = self.get_colorized_tag(buffer, text, color)
buffer.apply_tag(tag, start_itr, end_itr)
def get_colorized_tag(self, buffer, tag, color: Gdk.RGBA):
tag_table = buffer.get_tag_table()
colorize_tag = f"{self.tag_stub_name}_{tag}"
search_tag = tag_table.lookup(colorize_tag)
if not search_tag:
search_tag = buffer.create_tag(
colorize_tag, background_rgba = color
)
return search_tag
def clear_color_tags(self, buffer):
tag_table = buffer.get_tag_table()
def traverse_tags(tag, user_data):
name = tag.get_property("name")
if not name: return
if name.startswith(self.tag_stub_name):
user_data.append(tag)
tags = []
tag_table.foreach(traverse_tags, tags)
for tag in tags:
tag_table.remove(tag)

View File

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

View File

@@ -0,0 +1,58 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .colorize import Colorize
colorize = Colorize()
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.AddedNewFileEvent):
colorize.handle_colorize(event.file.buffer)
elif isinstance(event, Code_Event_Types.TextChangedEvent):
colorize.handle_colorize(event.buffer)
def load(self):
event = Event_Factory.create_event("register_command",
command_name = "tggle_colorize",
command = Handler,
binding_mode = "released",
binding = "<Shift><Control>c"
)
self.emit_to("source_views", event)
def run(self):
...
class Handler:
@staticmethod
def execute(
view: any,
*args,
**kwargs
):
logger.debug("Command: Toggle Colorize")
colorize.is_colorize_paused = not colorize.is_colorize_paused
if colorize.is_colorize_paused:
colorize.clear_color_tags( view.get_buffer() )
return
colorize.handle_colorize( view.get_buffer() )

View File

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

View File

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

View File

@@ -0,0 +1,66 @@
# Python imports
# Lib imports
# Application imports
from .mixins.code_comment_tags_mixin import CodeCommentTagsMixin
class Commenter(CodeCommentTagsMixin):
def __init__(self):
...
def keyboard_tggl_comment(self, buffer):
language = buffer.get_language()
if language is None: 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
bounds = buffer.get_selection_bounds()
if bounds:
self._bounds_comment(
start_tag, end_tag, bounds, buffer
)
else:
self._line_comment(start_tag, end_tag, buffer)
def _line_comment(self, start_tag, end_tag, buffer):
start_itr = buffer.get_iter_at_mark( buffer.get_insert() ).copy()
end_itr = start_itr.copy()
if not start_itr.starts_line():
start_itr.set_line_offset(0)
if not end_itr.ends_line():
end_itr.forward_to_line_end()
text = buffer.get_text(start_itr, end_itr, True)
text = text.replace(start_tag, "") if text.startswith(start_tag) else start_tag + text
buffer.begin_user_action()
buffer.delete(start_itr, end_itr)
buffer.insert(start_itr, text)
buffer.end_user_action()
def _bounds_comment(self, start_tag, end_tag, bounds, buffer):
start_itr, end_itr = bounds
if not start_itr.starts_line():
start_itr.set_line_offset(0)
if not end_itr.ends_line():
end_itr.forward_to_line_end()
text = buffer.get_text(start_itr, end_itr, True)
text = "\n".join(
line.replace(start_tag, "") if line.startswith(start_tag) else start_tag + line
for line in text.splitlines()
)
buffer.begin_user_action()
buffer.delete(start_itr, end_itr)
buffer.insert(start_itr, text)
buffer.end_user_action()

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
# Python imports
# Lib imports
# Application imports
class CodeCommentTagsMixin:
def get_comment_tags(self, language):
start_tag, end_tag = self.get_line_comment_tags(language)
if (start_tag, end_tag) == (None, None):
start_tag, end_tag = self.get_block_comment_tags(language)
return start_tag, end_tag
def get_block_comment_tags(self, language):
start_tag = language.get_metadata('block-comment-start')
end_tag = language.get_metadata('block-comment-end')
if start_tag and end_tag: return (start_tag, end_tag)
return (None, None)
def get_line_comment_tags(self, language):
start_tag = language.get_metadata('line-comment-start')
if start_tag: return (start_tag, None)
return (None, None)

View File

@@ -0,0 +1,51 @@
# 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 plugins.plugin_types import PluginCode
from .commenter import Commenter
commenter = Commenter()
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 = "keyboard_tggl_comment",
command = Handler,
binding_mode = "released",
binding = "<Control>slash"
)
self.emit_to("source_views", event)
def run(self):
...
class Handler:
@staticmethod
def execute(
view: any,
*args,
**kwargs
):
logger.debug("Command: Toggle Comment")
commenter.keyboard_tggl_comment( view.get_buffer() )

View File

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

View File

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

View File

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

View File

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