Moved plugins and refactor command system
- Moved plugins to apropriate sub folders - Refactor command system with new add_command method and rename GetCommandSystemEvent to GetNewCommandSystemEvent - Add RegisterCommandEvent for dynamic command registration - Change footer container orientation to VERTICAL - Add search-highlight tag to source buffer - Add file change detection (deleted, externally modified) in source_file - Add JSON prettify option to source view popup menu - Enable hexpand on VTE widget - Update plugins_controller_mixin to use widget_registry
This commit is contained in:
66
plugins/code/commentzar/commenter.py
Normal file
66
plugins/code/commentzar/commenter.py
Normal 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()
|
||||||
|
|
||||||
@@ -4,8 +4,5 @@
|
|||||||
"credit": "Hamad Al Marri",
|
"credit": "Hamad Al Marri",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"support": "",
|
"support": "",
|
||||||
"requests": {
|
"requests": {}
|
||||||
"pass_events": true,
|
|
||||||
"bind_keys": ["Commentzar||keyboard_tggl_comment:<Control>slash"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
3
plugins/code/commentzar/mixins/__init__.py
Normal file
3
plugins/code/commentzar/mixins/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Pligin Module Mixin
|
||||||
|
"""
|
||||||
30
plugins/code/commentzar/mixins/code_comment_tags_mixin.py
Executable file
30
plugins/code/commentzar/mixins/code_comment_tags_mixin.py
Executable 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)
|
||||||
49
plugins/code/commentzar/plugin.py
Normal file
49
plugins/code/commentzar/plugin.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# 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.message_to("source_views", event)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Handler:
|
||||||
|
@staticmethod
|
||||||
|
def execute(
|
||||||
|
view: any
|
||||||
|
):
|
||||||
|
logger.debug("Command: Toggle Comment")
|
||||||
|
commenter.keyboard_tggl_comment( view.get_buffer() )
|
||||||
@@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0')
|
|||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from libs.dto.base_event import BaseEvent
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
from libs.event_factory import Event_Factory
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
from plugins.plugin_types import PluginCode
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ class Plugin(PluginCode):
|
|||||||
self.provider: Provider = None
|
self.provider: Provider = None
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: BaseEvent):
|
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||||
...
|
...
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
@@ -30,13 +30,13 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
|||||||
return 'Example Code Completion'
|
return 'Example Code Completion'
|
||||||
|
|
||||||
def do_match(self, context):
|
def do_match(self, context):
|
||||||
# word = context.get_word()
|
# Note: If provider is in interactive activation then need to check
|
||||||
# if not word or len(word) < 2: return False
|
# 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
|
||||||
|
|
||||||
""" Get whether the provider match the context of completion detailed in context. """
|
|
||||||
word = self.response_cache.get_word(context)
|
word = self.response_cache.get_word(context)
|
||||||
if not word or len(word) < 2: return False
|
if not word or len(word) < 2: return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def do_get_priority(self):
|
def do_get_priority(self):
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
import re
|
import re
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
@@ -21,6 +22,9 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ProviderResponseCache, self).__init__()
|
super(ProviderResponseCache, self).__init__()
|
||||||
|
|
||||||
|
# Note: Using asyncio.run causes a keyboard trap that prevents app
|
||||||
|
# closure from terminal. ThreadPoolExecutor seems to not have such issues...
|
||||||
|
self.executor = ThreadPoolExecutor(max_workers = 1)
|
||||||
self.matchers: dict = {
|
self.matchers: dict = {
|
||||||
"hello": {
|
"hello": {
|
||||||
"label": "Hello, World!",
|
"label": "Hello, World!",
|
||||||
@@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0')
|
|||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from libs.dto.base_event import BaseEvent
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
from libs.event_factory import Event_Factory
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
from plugins.plugin_types import PluginCode
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ class Plugin(PluginCode):
|
|||||||
self.provider: Provider = None
|
self.provider: Provider = None
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: BaseEvent):
|
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||||
...
|
...
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
@@ -17,9 +18,13 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ProviderResponseCache, self).__init__()
|
super(ProviderResponseCache, self).__init__()
|
||||||
|
|
||||||
|
self.executor = ThreadPoolExecutor(max_workers = 1)
|
||||||
|
self.matchers: dict = {}
|
||||||
|
|
||||||
|
|
||||||
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
||||||
...
|
buffer = event.file.buffer
|
||||||
|
self.executor.submit(self._handle_change, buffer)
|
||||||
|
|
||||||
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
||||||
...
|
...
|
||||||
@@ -28,8 +33,13 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
|||||||
...
|
...
|
||||||
|
|
||||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
||||||
|
buffer = event.file.buffer
|
||||||
|
self.executor.submit(self._handle_change, buffer)
|
||||||
|
|
||||||
|
def _handle_change(self, buffer):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
def filter(self, word: str) -> list[dict]:
|
def filter(self, word: str) -> list[dict]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0')
|
|||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from libs.dto.base_event import BaseEvent
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
from libs.event_factory import Event_Factory
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
from plugins.plugin_types import PluginCode
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ class Plugin(PluginCode):
|
|||||||
self.provider: Provider = None
|
self.provider: Provider = None
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: BaseEvent):
|
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||||
...
|
...
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
@@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0')
|
|||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from libs.dto.base_event import BaseEvent
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
from libs.event_factory import Event_Factory
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
from plugins.plugin_types import PluginCode
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ class Plugin(PluginCode):
|
|||||||
self.provider: Provider = None
|
self.provider: Provider = None
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: BaseEvent):
|
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||||
...
|
...
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
@@ -6,8 +6,7 @@ gi.require_version('Gtk', '3.0')
|
|||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from libs.dto.base_event import BaseEvent
|
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||||
from libs.event_factory import Event_Factory
|
|
||||||
|
|
||||||
from plugins.plugin_types import PluginCode
|
from plugins.plugin_types import PluginCode
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ class Plugin(PluginCode):
|
|||||||
self.provider: Provider = None
|
self.provider: Provider = None
|
||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: BaseEvent):
|
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||||
...
|
...
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
@@ -30,6 +30,11 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider):
|
|||||||
return 'Words Completion'
|
return 'Words Completion'
|
||||||
|
|
||||||
def do_match(self, context):
|
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
|
||||||
|
|
||||||
word = self.response_cache.get_word(context)
|
word = self.response_cache.get_word(context)
|
||||||
if not word or len(word) < 2: return False
|
if not word or len(word) < 2: return False
|
||||||
return True
|
return True
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
import asyncio
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
@@ -24,7 +24,8 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
|||||||
|
|
||||||
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
|
||||||
buffer = event.file.buffer
|
buffer = event.file.buffer
|
||||||
asyncio.run( self._handle_change(buffer) )
|
with ThreadPoolExecutor(max_workers = 1) as executor:
|
||||||
|
executor.submit(self._handle_change, buffer)
|
||||||
|
|
||||||
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
|
||||||
self.matchers[event.file.buffer] = set()
|
self.matchers[event.file.buffer] = set()
|
||||||
@@ -35,9 +36,10 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
|||||||
|
|
||||||
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
def process_file_change(self, event: Code_Event_Types.TextChangedEvent):
|
||||||
buffer = event.file.buffer
|
buffer = event.file.buffer
|
||||||
asyncio.run( self._handle_change(buffer) )
|
with ThreadPoolExecutor(max_workers = 1) as executor:
|
||||||
|
executor.submit(self._handle_change, buffer)
|
||||||
|
|
||||||
async def _handle_change(self, buffer):
|
def _handle_change(self, buffer):
|
||||||
start_itr = buffer.get_start_iter()
|
start_itr = buffer.get_start_iter()
|
||||||
end_itr = buffer.get_end_iter()
|
end_itr = buffer.get_end_iter()
|
||||||
data = buffer.get_text(start_itr, end_itr, False)
|
data = buffer.get_text(start_itr, end_itr, False)
|
||||||
@@ -67,6 +69,9 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
|||||||
buffer = self.get_iter_correctly(context).get_buffer()
|
buffer = self.get_iter_correctly(context).get_buffer()
|
||||||
word = self.get_word(context).rstrip()
|
word = self.get_word(context).rstrip()
|
||||||
|
|
||||||
|
if not buffer in self.matchers:
|
||||||
|
self.matchers[buffer] = set()
|
||||||
|
|
||||||
response: list[dict] = []
|
response: list[dict] = []
|
||||||
for entry in self.matchers[buffer]:
|
for entry in self.matchers[buffer]:
|
||||||
if not entry.rstrip().startswith(word): continue
|
if not entry.rstrip().startswith(word): continue
|
||||||
@@ -81,7 +86,6 @@ class ProviderResponseCache(ProviderResponseCacheBase):
|
|||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def load_empty_set(self, buffer):
|
def load_empty_set(self, buffer):
|
||||||
self.matchers[buffer] = set()
|
self.matchers[buffer] = set()
|
||||||
|
|
||||||
BIN
plugins/code/search_replace/images/only-in-selection.png
Normal file
BIN
plugins/code/search_replace/images/only-in-selection.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
BIN
plugins/code/search_replace/images/whole-word.png
Normal file
BIN
plugins/code/search_replace/images/whole-word.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.5 KiB |
7
plugins/code/search_replace/manifest.json
Normal file
7
plugins/code/search_replace/manifest.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "Search/Replace",
|
||||||
|
"author": "ITDominator",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"support": "",
|
||||||
|
"requests": {}
|
||||||
|
}
|
||||||
3
plugins/code/search_replace/mixins/__init__.py
Normal file
3
plugins/code/search_replace/mixins/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Pligin Module Mixins
|
||||||
|
"""
|
||||||
50
plugins/code/search_replace/mixins/replace_mixin.py
Normal file
50
plugins/code/search_replace/mixins/replace_mixin.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ReplaceMixin:
|
||||||
|
|
||||||
|
def _replace_word(
|
||||||
|
self,
|
||||||
|
current_index: int,
|
||||||
|
to_text: str,
|
||||||
|
buffer: any
|
||||||
|
):
|
||||||
|
self.clear_highlight(buffer)
|
||||||
|
|
||||||
|
start_itr, end_itr = self.matches[current_index]
|
||||||
|
self.active_view.scroll_to_iter(end_itr, 0.2, False, 0, 0)
|
||||||
|
|
||||||
|
buffer.begin_user_action()
|
||||||
|
buffer.delete(start_itr, end_itr)
|
||||||
|
buffer.insert(start_itr, to_text)
|
||||||
|
buffer.end_user_action()
|
||||||
|
|
||||||
|
def _replace_all_words(self, to_text: str, buffer: any):
|
||||||
|
marks: list = []
|
||||||
|
|
||||||
|
for start_itr, end_itr in self.matches:
|
||||||
|
start_mark = buffer.create_mark(None, start_itr, left_gravity = True)
|
||||||
|
end_mark = buffer.create_mark(None, end_itr, left_gravity = False)
|
||||||
|
marks.append((start_mark, end_mark))
|
||||||
|
|
||||||
|
buffer.begin_user_action()
|
||||||
|
|
||||||
|
for start_mark, end_mark in reversed(marks):
|
||||||
|
start_itr = buffer.get_iter_at_mark(start_mark)
|
||||||
|
end_itr = buffer.get_iter_at_mark(end_mark)
|
||||||
|
|
||||||
|
buffer.delete(start_itr, end_itr)
|
||||||
|
buffer.insert(start_itr, to_text)
|
||||||
|
|
||||||
|
buffer.end_user_action()
|
||||||
|
|
||||||
|
for start_mark, end_mark in marks:
|
||||||
|
buffer.delete_mark(start_mark)
|
||||||
|
buffer.delete_mark(end_mark)
|
||||||
|
|
||||||
|
self.find_entry.grab_focus()
|
||||||
79
plugins/code/search_replace/mixins/search_mixin.py
Normal file
79
plugins/code/search_replace/mixins/search_mixin.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# Python imports
|
||||||
|
from contextlib import suppress
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SearchMixin:
|
||||||
|
def is_word_char(self, ch):
|
||||||
|
return ch.isalnum() or ch == "_"
|
||||||
|
|
||||||
|
def is_whole_word(self, start_itr, end_itr, buffer):
|
||||||
|
if start_itr.backward_char():
|
||||||
|
prev_char = start_itr.get_char()
|
||||||
|
if self.is_word_char(prev_char):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# if end_itr.forward_char():
|
||||||
|
# next_char = end_itr.get_char()
|
||||||
|
# if self.is_word_char(next_char):
|
||||||
|
# return False
|
||||||
|
next_char = end_itr.get_char()
|
||||||
|
if self.is_word_char(next_char):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _find_all_matches(self, search_text, buffer):
|
||||||
|
self.matches.clear()
|
||||||
|
self.current_index = -1
|
||||||
|
|
||||||
|
start_itr = buffer.get_start_iter()
|
||||||
|
end_itr = buffer.get_end_iter()
|
||||||
|
|
||||||
|
case_mode = Gtk.TextSearchFlags.CASE_INSENSITIVE if not self.mode_bttn_box.match_case else Gtk.TextSearchFlags.TEXT_ONLY
|
||||||
|
whole_word = self.mode_bttn_box.whole_word
|
||||||
|
if self.mode_bttn_box.in_selection:
|
||||||
|
with suppress(Exception):
|
||||||
|
start_itr, end_itr = buffer.get_selection_bounds()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
match = start_itr.forward_search(
|
||||||
|
search_text,
|
||||||
|
case_mode,
|
||||||
|
end_itr
|
||||||
|
)
|
||||||
|
|
||||||
|
if not match: break
|
||||||
|
|
||||||
|
match_start, match_end = match
|
||||||
|
if whole_word and not self.is_whole_word(match_start.copy(), match_end.copy(), buffer):
|
||||||
|
start_itr = match_end
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.matches.append(
|
||||||
|
(match_start.copy(), match_end.copy())
|
||||||
|
)
|
||||||
|
|
||||||
|
start_itr = match_end
|
||||||
|
|
||||||
|
def _highlight_all_matches(self, buffer):
|
||||||
|
self.clear_highlight(buffer)
|
||||||
|
|
||||||
|
for start_itr, end_itr in self.matches:
|
||||||
|
buffer.apply_tag(self.highlight_tag, start_itr, end_itr)
|
||||||
|
|
||||||
|
def _search_for_next_word(self, buffer):
|
||||||
|
self.current_index = (self.current_index + 1) % len(self.matches)
|
||||||
|
self._highlight_current(self.current_index, buffer)
|
||||||
|
|
||||||
|
def _search_for_prev_word(self, buffer):
|
||||||
|
self.current_index = (self.current_index - 1) % len(self.matches)
|
||||||
|
self._highlight_current(self.current_index, buffer)
|
||||||
88
plugins/code/search_replace/mixins/search_replace_mixin.py
Normal file
88
plugins/code/search_replace/mixins/search_replace_mixin.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gdk', '3.0')
|
||||||
|
from gi.repository import Gdk
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from .search_mixin import SearchMixin
|
||||||
|
from .replace_mixin import ReplaceMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SearchReplaceMixin(SearchMixin, ReplaceMixin):
|
||||||
|
|
||||||
|
def _find_entry_focus_in_event(self, entry, event):
|
||||||
|
search_text = entry.get_text()
|
||||||
|
buffer = self.active_view.get_buffer()
|
||||||
|
|
||||||
|
if buffer.get_has_selection() and not search_text:
|
||||||
|
if not self.mode_bttn_box.in_selection:
|
||||||
|
start_itr, end_itr = buffer.get_selection_bounds()
|
||||||
|
|
||||||
|
entry.set_text(
|
||||||
|
buffer.get_text(
|
||||||
|
start_itr,
|
||||||
|
end_itr,
|
||||||
|
include_hidden_chars = False
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def _find_entry_search_change(self, entry):
|
||||||
|
search_text = entry.get_text()
|
||||||
|
buffer = self.active_view.get_buffer()
|
||||||
|
self.highlight_tag = buffer.get_tag_table().lookup("search-highlight")
|
||||||
|
|
||||||
|
if not search_text:
|
||||||
|
self.clear_highlight(buffer)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._find_all_matches(search_text, buffer)
|
||||||
|
self._highlight_all_matches(buffer)
|
||||||
|
|
||||||
|
def _find_entry_activate(self, entry):
|
||||||
|
self._find_entry_next_match(entry)
|
||||||
|
|
||||||
|
def _find_entry_next_match(self, entry):
|
||||||
|
search_text = entry.get_text()
|
||||||
|
|
||||||
|
if not search_text: return
|
||||||
|
|
||||||
|
buffer = self.active_view.get_buffer()
|
||||||
|
self._search_for_next_word(buffer)
|
||||||
|
|
||||||
|
def _find_entry_previous_match(self, entry):
|
||||||
|
search_text = entry.get_text()
|
||||||
|
|
||||||
|
if not search_text: return
|
||||||
|
|
||||||
|
buffer = self.active_view.get_buffer()
|
||||||
|
self._search_for_prev_word(buffer)
|
||||||
|
|
||||||
|
def _replace_entry_activate(self, entry):
|
||||||
|
to_text = entry.get_text()
|
||||||
|
|
||||||
|
if not to_text: return
|
||||||
|
|
||||||
|
buffer = self.active_view.get_buffer()
|
||||||
|
self._replace_word(self.current_index, to_text, buffer)
|
||||||
|
self._find_entry_search_change(self.find_entry)
|
||||||
|
|
||||||
|
def _replace_all_activate(self, entry):
|
||||||
|
to_text = entry.get_text()
|
||||||
|
|
||||||
|
if not to_text: return
|
||||||
|
|
||||||
|
buffer = self.active_view.get_buffer()
|
||||||
|
self._replace_all_words(to_text, buffer)
|
||||||
|
|
||||||
|
def _highlight_current(self, current_index, buffer):
|
||||||
|
self.clear_highlight(buffer)
|
||||||
|
|
||||||
|
start_itr, end_itr = self.matches[current_index]
|
||||||
|
buffer.apply_tag(self.highlight_tag, start_itr, end_itr)
|
||||||
|
|
||||||
|
self.active_view.scroll_to_iter(end_itr, 0.2, False, 0, 0)
|
||||||
75
plugins/code/search_replace/mode_buttons.py
Normal file
75
plugins/code/search_replace/mode_buttons.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ModeException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ModeButtons(Gtk.ButtonBox):
|
||||||
|
def __init__(self):
|
||||||
|
super(ModeButtons, self).__init__()
|
||||||
|
|
||||||
|
self.use_regex: bool = False
|
||||||
|
self.match_case: bool = False
|
||||||
|
self.in_selection: bool = False
|
||||||
|
self.whole_word: bool = False
|
||||||
|
|
||||||
|
self._setup_styling()
|
||||||
|
self._setup_signals()
|
||||||
|
self._load_widgets()
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_styling(self):
|
||||||
|
ctx = self.get_style_context()
|
||||||
|
ctx.add_class("search-replace-mode-buttons")
|
||||||
|
|
||||||
|
def _setup_signals(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _load_widgets(self):
|
||||||
|
use_regex_bttn = Gtk.ToggleButton(label = ".*")
|
||||||
|
match_case_bttn = Gtk.ToggleButton(label = "Aa")
|
||||||
|
in_selection_bttn = Gtk.ToggleButton()
|
||||||
|
whole_word_bttn = Gtk.ToggleButton()
|
||||||
|
|
||||||
|
use_regex_bttn.set_sensitive(False)
|
||||||
|
|
||||||
|
use_regex_bttn.set_tooltip_text("Use Regex")
|
||||||
|
match_case_bttn.set_tooltip_text("Match Case")
|
||||||
|
in_selection_bttn.set_tooltip_text("Only In Selection")
|
||||||
|
whole_word_bttn.set_tooltip_text("Whole Word")
|
||||||
|
|
||||||
|
use_regex_bttn.connect("toggled", self._toggled_button, "use_regex")
|
||||||
|
match_case_bttn.connect("toggled", self._toggled_button, "match_case")
|
||||||
|
in_selection_bttn.connect("toggled", self._toggled_button, "in_selection")
|
||||||
|
whole_word_bttn.connect("toggled", self._toggled_button, "whole_word")
|
||||||
|
|
||||||
|
in_selection_bttn.set_image(
|
||||||
|
Gtk.Image.new_from_file("images/only-in-selection.png")
|
||||||
|
)
|
||||||
|
whole_word_bttn.set_image(
|
||||||
|
Gtk.Image.new_from_file("images/whole-word.png")
|
||||||
|
)
|
||||||
|
|
||||||
|
self.add(use_regex_bttn)
|
||||||
|
self.add(match_case_bttn)
|
||||||
|
self.add(in_selection_bttn)
|
||||||
|
self.add(whole_word_bttn)
|
||||||
|
|
||||||
|
def _toggled_button(self, toggle_button, mode: str):
|
||||||
|
setattr(self, mode, not getattr(self, mode))
|
||||||
|
self.request_update()
|
||||||
|
|
||||||
|
def request_update(self):
|
||||||
|
raise ModeException("Must by 'monkey' patched from search_replace.py")
|
||||||
|
|
||||||
|
|
||||||
65
plugins/code/search_replace/plugin.py
Normal file
65
plugins/code/search_replace/plugin.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# 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 .search_replace import SearchReplace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
search_replace = SearchReplace()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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.FocusedViewEvent):
|
||||||
|
self._handle_view_change(event)
|
||||||
|
|
||||||
|
def _handle_view_change(self, event: Code_Event_Types.FocusedViewEvent):
|
||||||
|
if search_replace.is_visible():
|
||||||
|
buffer = search_replace.active_view.get_buffer()
|
||||||
|
search_replace.clear_highlight(buffer)
|
||||||
|
|
||||||
|
search_replace.active_view = event.view
|
||||||
|
|
||||||
|
if search_replace.is_visible():
|
||||||
|
search_replace._find_entry_search_change(
|
||||||
|
search_replace.find_entry
|
||||||
|
)
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
footer = self.requests_ui_element("footer-container")
|
||||||
|
footer.add( search_replace )
|
||||||
|
|
||||||
|
event = Event_Factory.create_event("register_command",
|
||||||
|
command_name = "search_replace",
|
||||||
|
command = Handler,
|
||||||
|
binding_mode = "released",
|
||||||
|
binding = "<Control>f"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.message_to("source_views", event)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class Handler:
|
||||||
|
@staticmethod
|
||||||
|
def execute(
|
||||||
|
view: any
|
||||||
|
):
|
||||||
|
logger.debug("Command: Search/Replace")
|
||||||
|
search_replace.hide() if search_replace.is_visible() else search_replace.show()
|
||||||
152
plugins/code/search_replace/search_replace.py
Normal file
152
plugins/code/search_replace/search_replace.py
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# 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 .mixins.search_replace_mixin import SearchReplaceMixin
|
||||||
|
|
||||||
|
from .mode_buttons import ModeButtons
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SearchReplace(Gtk.Grid, SearchReplaceMixin):
|
||||||
|
def __init__(self):
|
||||||
|
super(SearchReplace, self).__init__()
|
||||||
|
|
||||||
|
self.active_view = None
|
||||||
|
self.highlight_tag = None
|
||||||
|
self.matches: list[tuple] = []
|
||||||
|
|
||||||
|
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("search-replace")
|
||||||
|
|
||||||
|
self.set_hexpand(True)
|
||||||
|
self.set_column_spacing(15)
|
||||||
|
self.set_row_spacing(15)
|
||||||
|
self.set_row_spacing(15)
|
||||||
|
|
||||||
|
self.set_margin_start(15)
|
||||||
|
self.set_margin_end(15)
|
||||||
|
self.set_margin_top(15)
|
||||||
|
self.set_margin_bottom(15)
|
||||||
|
|
||||||
|
def _setup_signals(self):
|
||||||
|
self.connect("show", self._handle_show)
|
||||||
|
self.connect("hide", self._handle_hide)
|
||||||
|
|
||||||
|
def _load_widgets(self):
|
||||||
|
self.stateus_lbl = Gtk.Label(label = "Find in Current Buffer")
|
||||||
|
self.find_options_lbl = Gtk.Label(label = "Finding with Options: Case Insensitive")
|
||||||
|
self.mode_bttn_box = ModeButtons()
|
||||||
|
|
||||||
|
self.find_entry = Gtk.SearchEntry()
|
||||||
|
self.replace_entry = Gtk.SearchEntry()
|
||||||
|
find_bttn = Gtk.Button(label = "Find")
|
||||||
|
find_all_bttn = Gtk.Button(label = "Find All")
|
||||||
|
replace_bttn = Gtk.Button(label = "Replace")
|
||||||
|
replace_all_bttn = Gtk.Button(label = "Replace All")
|
||||||
|
|
||||||
|
self.find_entry.set_hexpand(True)
|
||||||
|
self.replace_entry.set_hexpand(True)
|
||||||
|
self.find_entry.set_max_width_chars(16)
|
||||||
|
self.replace_entry.set_max_width_chars(16)
|
||||||
|
self.find_entry.set_placeholder_text("Find in current buffer...")
|
||||||
|
self.replace_entry.set_placeholder_text("Replace in current buffer...")
|
||||||
|
|
||||||
|
self.mode_bttn_box.request_update = self.request_update
|
||||||
|
|
||||||
|
self.find_entry.connect("focus-in-event", self._find_entry_focus_in_event)
|
||||||
|
self.find_entry.connect("key-release-event", self._find_entry_key_release_event)
|
||||||
|
self.find_entry.connect("activate", self._find_entry_activate)
|
||||||
|
self.find_entry.connect("search-changed", self._find_entry_search_change)
|
||||||
|
self.find_entry.connect("next-match", self._find_entry_next_match)
|
||||||
|
self.find_entry.connect("previous-match", self._find_entry_previous_match)
|
||||||
|
|
||||||
|
self.replace_entry.connect("key-release-event", self._replace_entry_key_release_event)
|
||||||
|
self.replace_entry.connect("activate", self._replace_entry_activate)
|
||||||
|
|
||||||
|
find_bttn.connect(
|
||||||
|
"clicked",
|
||||||
|
lambda button: self._find_entry_next_match(self.find_entry)
|
||||||
|
)
|
||||||
|
find_all_bttn.connect(
|
||||||
|
"clicked",
|
||||||
|
lambda button: self._find_entry_search_change(self.find_entry)
|
||||||
|
)
|
||||||
|
replace_bttn.connect(
|
||||||
|
"clicked",
|
||||||
|
lambda button: self._replace_entry_activate(self.replace_entry)
|
||||||
|
)
|
||||||
|
replace_all_bttn.connect(
|
||||||
|
"clicked",
|
||||||
|
lambda button: self._replace_all_activate(self.replace_entry)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.attach(child = self.stateus_lbl, left = 0, top = 0, width = 2, height = 1)
|
||||||
|
self.attach(child = self.find_options_lbl, left = 2, top = 0, width = 2, height = 1)
|
||||||
|
self.attach(child = self.mode_bttn_box, left = 4, top = 0, width = 2, height = 1)
|
||||||
|
|
||||||
|
self.attach(child = self.find_entry, left = 0, top = 1, width = 4, height = 1)
|
||||||
|
self.attach(child = find_bttn, left = 4, top = 1, width = 1, height = 1)
|
||||||
|
self.attach(child = find_all_bttn, left = 5, top = 1, width = 1, height = 1)
|
||||||
|
|
||||||
|
self.attach(child = self.replace_entry, left = 0, top = 2, width = 4, height = 1)
|
||||||
|
self.attach(child = replace_bttn, left = 4, top = 2, width = 1, height = 1)
|
||||||
|
self.attach(child = replace_all_bttn, left = 5, top = 2, width = 1, height = 1)
|
||||||
|
|
||||||
|
def _handle_show(self, widget):
|
||||||
|
self.find_entry.set_text("")
|
||||||
|
self.find_entry.grab_focus()
|
||||||
|
|
||||||
|
def _handle_hide(self, widget):
|
||||||
|
if not self.active_view: return
|
||||||
|
|
||||||
|
buffer = self.active_view.get_buffer()
|
||||||
|
self.clear_highlight(buffer)
|
||||||
|
self.active_view.grab_focus()
|
||||||
|
|
||||||
|
def request_update(self):
|
||||||
|
self._find_entry_search_change(self.find_entry)
|
||||||
|
|
||||||
|
def _find_entry_key_release_event(self, widget, event):
|
||||||
|
modifiers = Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK)
|
||||||
|
is_control = True if modifiers & Gdk.ModifierType.CONTROL_MASK else False
|
||||||
|
is_shift = True if modifiers & Gdk.ModifierType.SHIFT_MASK else False
|
||||||
|
keyname = Gdk.keyval_name(event.keyval).lower()
|
||||||
|
|
||||||
|
if is_control and keyname == "f":
|
||||||
|
self.hide()
|
||||||
|
elif is_control and keyname == "r":
|
||||||
|
self.replace_entry.grab_focus()
|
||||||
|
|
||||||
|
def _replace_entry_key_release_event(self, widget, event):
|
||||||
|
modifiers = Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK)
|
||||||
|
is_control = True if modifiers & Gdk.ModifierType.CONTROL_MASK else False
|
||||||
|
is_shift = True if modifiers & Gdk.ModifierType.SHIFT_MASK else False
|
||||||
|
keyname = Gdk.keyval_name(event.keyval).lower()
|
||||||
|
|
||||||
|
if is_control and keyname == "l":
|
||||||
|
self.find_entry.grab_focus()
|
||||||
|
elif is_control and keyname == "f":
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
def clear_highlight(self, buffer):
|
||||||
|
if not self.highlight_tag: return
|
||||||
|
start, end = buffer.get_bounds()
|
||||||
|
buffer.remove_tag(self.highlight_tag, start, end)
|
||||||
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AddCommentMixin:
|
|
||||||
def add_comment_characters(self, buffer, start_tag, end_tag, start, end, deselect, oldPos):
|
|
||||||
smark = buffer.create_mark("start", start, False)
|
|
||||||
imark = buffer.create_mark("iter", start, False)
|
|
||||||
emark = buffer.create_mark("end", end, False)
|
|
||||||
number_lines = end.get_line() - start.get_line() + 1
|
|
||||||
comment_pos_iter = None
|
|
||||||
count = 0
|
|
||||||
|
|
||||||
buffer.begin_user_action()
|
|
||||||
|
|
||||||
for i in range(0, number_lines):
|
|
||||||
iter = buffer.get_iter_at_mark(imark)
|
|
||||||
if not comment_pos_iter:
|
|
||||||
(comment_pos_iter, count) = self.discard_white_spaces(iter)
|
|
||||||
|
|
||||||
if self.is_commented(comment_pos_iter, start_tag):
|
|
||||||
new_code = self.remove_comment_characters(buffer, start_tag, end_tag, start, end)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
comment_pos_iter = iter
|
|
||||||
for i in range(count):
|
|
||||||
c = iter.get_char()
|
|
||||||
if not c in (" ", "\t"):
|
|
||||||
break
|
|
||||||
|
|
||||||
iter.forward_char()
|
|
||||||
|
|
||||||
buffer.insert(comment_pos_iter, start_tag)
|
|
||||||
buffer.insert(comment_pos_iter, " ")
|
|
||||||
|
|
||||||
if end_tag:
|
|
||||||
if i != number_lines -1:
|
|
||||||
iter = buffer.get_iter_at_mark(imark)
|
|
||||||
iter.forward_to_line_end()
|
|
||||||
buffer.insert(iter, end_tag)
|
|
||||||
else:
|
|
||||||
iter = buffer.get_iter_at_mark(emark)
|
|
||||||
buffer.insert(iter, end_tag)
|
|
||||||
|
|
||||||
iter = buffer.get_iter_at_mark(imark)
|
|
||||||
iter.forward_line()
|
|
||||||
buffer.delete_mark(imark)
|
|
||||||
imark = buffer.create_mark("iter", iter, True)
|
|
||||||
|
|
||||||
buffer.end_user_action()
|
|
||||||
|
|
||||||
buffer.delete_mark(imark)
|
|
||||||
new_start = buffer.get_iter_at_mark(smark)
|
|
||||||
new_end = buffer.get_iter_at_mark(emark)
|
|
||||||
|
|
||||||
buffer.select_range(new_start, new_end)
|
|
||||||
buffer.delete_mark(smark)
|
|
||||||
buffer.delete_mark(emark)
|
|
||||||
|
|
||||||
if deselect:
|
|
||||||
oldPosIter = buffer.get_iter_at_offset(oldPos + 2)
|
|
||||||
buffer.place_cursor(oldPosIter)
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class CodeCommentTags:
|
|
||||||
def get_comment_tags(self, lang):
|
|
||||||
(s, e) = self.get_line_comment_tags(lang)
|
|
||||||
if (s, e) == (None, None):
|
|
||||||
(s, e) = self.get_block_comment_tags(lang)
|
|
||||||
|
|
||||||
return (s, e)
|
|
||||||
|
|
||||||
def get_block_comment_tags(self, lang):
|
|
||||||
start_tag = lang.get_metadata('block-comment-start')
|
|
||||||
end_tag = lang.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, lang):
|
|
||||||
start_tag = lang.get_metadata('line-comment-start')
|
|
||||||
if start_tag:
|
|
||||||
return (start_tag, None)
|
|
||||||
|
|
||||||
return (None, None)
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from plugins.plugin_base import PluginBase
|
|
||||||
from .codecomment_tags import CodeCommentTags
|
|
||||||
from .remove_comment_mixin import RemoveCommentMixin
|
|
||||||
from .add_comment_mixin import AddCommentMixin
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(AddCommentMixin, RemoveCommentMixin, CodeCommentTags, PluginBase):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.name = "Commentzar" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
|
|
||||||
# where self.name should not be needed for message comms
|
|
||||||
|
|
||||||
|
|
||||||
def generate_reference_ui_element(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def subscribe_to_events(self):
|
|
||||||
self._event_system.subscribe("keyboard_tggl_comment", self._keyboard_tggl_comment)
|
|
||||||
self._event_system.subscribe("set_active_src_view", self._set_active_src_view)
|
|
||||||
|
|
||||||
def _set_active_src_view(self, source_view):
|
|
||||||
self._active_src_view = source_view
|
|
||||||
self._buffer = self._active_src_view.get_buffer()
|
|
||||||
self._tag_table = self._buffer.get_tag_table()
|
|
||||||
|
|
||||||
|
|
||||||
def _keyboard_tggl_comment(self):
|
|
||||||
buffer = self._buffer
|
|
||||||
lang = buffer.get_language()
|
|
||||||
if lang is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
(start_tag, end_tag) = self.get_comment_tags(lang)
|
|
||||||
if not start_tag and not end_tag:
|
|
||||||
return
|
|
||||||
|
|
||||||
sel = buffer.get_selection_bounds()
|
|
||||||
currentPosMark = buffer.get_insert()
|
|
||||||
oldPos = 0
|
|
||||||
|
|
||||||
# if user selected chars or multilines
|
|
||||||
if sel != ():
|
|
||||||
deselect = False
|
|
||||||
(start, end) = sel
|
|
||||||
if not start.starts_line():
|
|
||||||
start.set_line_offset(0)
|
|
||||||
if not end.ends_line():
|
|
||||||
end.forward_to_line_end()
|
|
||||||
else:
|
|
||||||
deselect = True
|
|
||||||
start = buffer.get_iter_at_mark(currentPosMark)
|
|
||||||
oldPos = buffer.get_iter_at_mark(currentPosMark).get_offset()
|
|
||||||
start.set_line_offset(0)
|
|
||||||
end = start.copy()
|
|
||||||
|
|
||||||
if not end.ends_line():
|
|
||||||
end.forward_to_line_end()
|
|
||||||
|
|
||||||
if start.get_offset() == end.get_offset():
|
|
||||||
buffer.begin_user_action()
|
|
||||||
buffer.insert(start, start_tag)
|
|
||||||
buffer.insert(start, " ")
|
|
||||||
buffer.end_user_action()
|
|
||||||
return
|
|
||||||
|
|
||||||
self._event_system.emit("pause_event_processing")
|
|
||||||
new_code = self.add_comment_characters(buffer, start_tag, end_tag, start, end, deselect, oldPos)
|
|
||||||
self._event_system.emit("resume_event_processing")
|
|
||||||
|
|
||||||
def discard_white_spaces(self, iter):
|
|
||||||
count = 0
|
|
||||||
while not iter.ends_line():
|
|
||||||
c = iter.get_char()
|
|
||||||
if not c in (" ", "\t"):
|
|
||||||
return (iter, count)
|
|
||||||
|
|
||||||
iter.forward_char()
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
return (iter, 0)
|
|
||||||
|
|
||||||
def is_commented(self, comment_pos_iter, start_tag):
|
|
||||||
head_iter = comment_pos_iter.copy()
|
|
||||||
self.forward_tag(head_iter, start_tag)
|
|
||||||
s = comment_pos_iter.get_slice(head_iter)
|
|
||||||
if s == start_tag:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
def forward_tag(self, iter, tag):
|
|
||||||
iter.forward_chars(len(tag))
|
|
||||||
|
|
||||||
def backward_tag(self, iter, tag):
|
|
||||||
iter.backward_chars(len(tag))
|
|
||||||
|
|
||||||
def get_tag_position_in_line(self, tag, head_iter, iter):
|
|
||||||
while not iter.ends_line():
|
|
||||||
s = iter.get_slice(head_iter)
|
|
||||||
if s == tag:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
head_iter.forward_char()
|
|
||||||
iter.forward_char()
|
|
||||||
return False
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RemoveCommentMixin:
|
|
||||||
def remove_comment_characters(self, buffer, start_tag, end_tag, start, end):
|
|
||||||
smark = buffer.create_mark("start", start, False)
|
|
||||||
emark = buffer.create_mark("end", end, False)
|
|
||||||
number_lines = end.get_line() - start.get_line() + 1
|
|
||||||
iter = start.copy()
|
|
||||||
head_iter = iter.copy()
|
|
||||||
self.forward_tag(head_iter, start_tag)
|
|
||||||
|
|
||||||
buffer.begin_user_action()
|
|
||||||
|
|
||||||
for i in range(0, number_lines):
|
|
||||||
if self.get_tag_position_in_line(start_tag, head_iter, iter):
|
|
||||||
dmark = buffer.create_mark("delete", iter, False)
|
|
||||||
buffer.delete(iter, head_iter)
|
|
||||||
|
|
||||||
space_iter = head_iter.copy()
|
|
||||||
space_iter.forward_char()
|
|
||||||
s = head_iter.get_slice(space_iter)
|
|
||||||
if s == " ":
|
|
||||||
buffer.delete(head_iter, space_iter)
|
|
||||||
|
|
||||||
if end_tag:
|
|
||||||
iter = buffer.get_iter_at_mark(dmark)
|
|
||||||
head_iter = iter.copy()
|
|
||||||
self.forward_tag(head_iter, end_tag)
|
|
||||||
if self.get_tag_position_in_line(end_tag, head_iter, iter):
|
|
||||||
buffer.delete(iter, head_iter)
|
|
||||||
buffer.delete_mark(dmark)
|
|
||||||
|
|
||||||
iter = buffer.get_iter_at_mark(smark)
|
|
||||||
iter.forward_line()
|
|
||||||
buffer.delete_mark(smark)
|
|
||||||
head_iter = iter.copy()
|
|
||||||
self.forward_tag(head_iter, start_tag)
|
|
||||||
smark = buffer.create_mark("iter", iter, True)
|
|
||||||
|
|
||||||
buffer.end_user_action()
|
|
||||||
|
|
||||||
buffer.delete_mark(smark)
|
|
||||||
buffer.delete_mark(emark)
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Search/Replace",
|
|
||||||
"author": "ITDominator",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"support": "",
|
|
||||||
"requests": {
|
|
||||||
"pass_events": true,
|
|
||||||
"pass_ui_objects": ["separator_botton"],
|
|
||||||
"bind_keys": ["Search/Replace||tggl_search_replace:<Control>f"]
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import threading
|
|
||||||
|
|
||||||
# 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
|
|
||||||
from gi.repository import GLib
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from plugins.plugin_base import PluginBase
|
|
||||||
from .styling_mixin import StylingMixin
|
|
||||||
from .replace_mixin import ReplaceMixin
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin(StylingMixin, ReplaceMixin, PluginBase):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.name = "Search/Replace" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
|
|
||||||
# where self.name should not be needed for message comms
|
|
||||||
self.path = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
self._GLADE_FILE = f"{self.path}/search_replace.glade"
|
|
||||||
|
|
||||||
self._search_replace_dialog = None
|
|
||||||
self._find_entry = None
|
|
||||||
self._replace_entry = None
|
|
||||||
self._active_src_view = None
|
|
||||||
self._buffer = None
|
|
||||||
self._tag_table = None
|
|
||||||
|
|
||||||
self.use_regex = False
|
|
||||||
self.use_case_sensitive = False
|
|
||||||
self.search_only_in_selection = False
|
|
||||||
self.use_whole_word_search = False
|
|
||||||
|
|
||||||
self.timer = None
|
|
||||||
self.search_time = 0.35
|
|
||||||
self.find_text = ""
|
|
||||||
self.search_tag = "search_tag"
|
|
||||||
self.highlight_color = "#FBF719"
|
|
||||||
self.text_color = "#000000"
|
|
||||||
self.alpha_num_under = re.compile(r"[a-zA-Z0-9_]")
|
|
||||||
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self._builder = Gtk.Builder()
|
|
||||||
self._builder.add_from_file(self._GLADE_FILE)
|
|
||||||
self._connect_builder_signals(self, self._builder)
|
|
||||||
|
|
||||||
separator_botton = self._ui_objects[0]
|
|
||||||
self._search_replace_dialog = self._builder.get_object("search_replace_dialog")
|
|
||||||
self._find_status_lbl = self._builder.get_object("find_status_lbl")
|
|
||||||
self._find_options_lbl = self._builder.get_object("find_options_lbl")
|
|
||||||
|
|
||||||
self._find_entry = self._builder.get_object("find_entry")
|
|
||||||
self._replace_entry = self._builder.get_object("replace_entry")
|
|
||||||
|
|
||||||
self._search_replace_dialog.set_relative_to(separator_botton)
|
|
||||||
self._search_replace_dialog.set_hexpand(True)
|
|
||||||
|
|
||||||
def generate_reference_ui_element(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
def subscribe_to_events(self):
|
|
||||||
self._event_system.subscribe("tggl_search_replace", self._tggl_search_replace)
|
|
||||||
self._event_system.subscribe("set_active_src_view", self._set_active_src_view)
|
|
||||||
|
|
||||||
def _set_active_src_view(self, source_view):
|
|
||||||
self._active_src_view = source_view
|
|
||||||
self._buffer = self._active_src_view.get_buffer()
|
|
||||||
self._tag_table = self._buffer.get_tag_table()
|
|
||||||
self.search_for_string(self._find_entry)
|
|
||||||
|
|
||||||
def _show_search_replace(self, widget = None, eve = None):
|
|
||||||
self._search_replace_dialog.popup()
|
|
||||||
|
|
||||||
def _tggl_search_replace(self, widget = None, eve = None):
|
|
||||||
is_visible = self._search_replace_dialog.is_visible()
|
|
||||||
buffer = self._active_src_view.get_buffer()
|
|
||||||
data = None
|
|
||||||
|
|
||||||
if buffer.get_has_selection():
|
|
||||||
start, end = buffer.get_selection_bounds()
|
|
||||||
data = buffer.get_text(start, end, include_hidden_chars = False)
|
|
||||||
|
|
||||||
if data:
|
|
||||||
self._find_entry.set_text(data)
|
|
||||||
|
|
||||||
if not is_visible:
|
|
||||||
self._search_replace_dialog.popup();
|
|
||||||
self._find_entry.grab_focus()
|
|
||||||
elif not data and is_visible:
|
|
||||||
self._search_replace_dialog.popdown()
|
|
||||||
self._find_entry.set_text("")
|
|
||||||
else:
|
|
||||||
self._find_entry.grab_focus()
|
|
||||||
|
|
||||||
|
|
||||||
def get_search_tag(self, buffer):
|
|
||||||
tag_table = buffer.get_tag_table()
|
|
||||||
search_tag = tag_table.lookup(self.search_tag)
|
|
||||||
if not search_tag:
|
|
||||||
search_tag = buffer.create_tag(self.search_tag, background = self.highlight_color, foreground = self.text_color)
|
|
||||||
|
|
||||||
buffer.remove_tag_by_name(self.search_tag, buffer.get_start_iter(), buffer.get_end_iter())
|
|
||||||
return search_tag
|
|
||||||
|
|
||||||
|
|
||||||
def cancel_timer(self):
|
|
||||||
if self.timer:
|
|
||||||
self.timer.cancel()
|
|
||||||
GLib.idle_remove_by_data(None)
|
|
||||||
|
|
||||||
def delay_search_glib(self):
|
|
||||||
GLib.idle_add(self._do_highlight)
|
|
||||||
|
|
||||||
def delay_search(self):
|
|
||||||
wait_time = self.search_time / len(self.find_text)
|
|
||||||
wait_time = max(wait_time, 0.05)
|
|
||||||
|
|
||||||
self.timer = threading.Timer(wait_time, self.delay_search_glib)
|
|
||||||
self.timer.daemon = True
|
|
||||||
self.timer.start()
|
|
||||||
|
|
||||||
|
|
||||||
def on_enter_search(self, widget, eve):
|
|
||||||
text = widget.get_text()
|
|
||||||
if not text: return
|
|
||||||
|
|
||||||
keyname = Gdk.keyval_name(eve.keyval)
|
|
||||||
if keyname == "Return":
|
|
||||||
self.find_next(widget)
|
|
||||||
|
|
||||||
def search_for_string(self, widget):
|
|
||||||
self.cancel_timer()
|
|
||||||
|
|
||||||
self.find_text = widget.get_text()
|
|
||||||
if len(self.find_text) > 0 and len(self.find_text) < 5:
|
|
||||||
self.delay_search()
|
|
||||||
else:
|
|
||||||
self._do_highlight(self.find_text)
|
|
||||||
|
|
||||||
|
|
||||||
def _do_highlight(self, query = None):
|
|
||||||
query = self.find_text if not query else query
|
|
||||||
buffer = self._active_src_view.get_buffer()
|
|
||||||
# Also clears tag from buffer so if no query we're clean in ui
|
|
||||||
search_tag = self.get_search_tag(buffer)
|
|
||||||
|
|
||||||
self.update_style(1)
|
|
||||||
if not query:
|
|
||||||
self._find_status_lbl.set_label(f"Find in current buffer")
|
|
||||||
self.update_style(0)
|
|
||||||
return
|
|
||||||
|
|
||||||
start_itr = buffer.get_start_iter()
|
|
||||||
end_itr = buffer.get_end_iter()
|
|
||||||
|
|
||||||
results, total_count = self.search(start_itr, query)
|
|
||||||
self._update_status_lbl(total_count, query)
|
|
||||||
for start, end in results:
|
|
||||||
buffer.apply_tag(search_tag, start, end)
|
|
||||||
|
|
||||||
def search(self, start_itr = None, query = None, limit = None):
|
|
||||||
if not start_itr or not query: return None, None
|
|
||||||
|
|
||||||
flags = Gtk.TextSearchFlags.VISIBLE_ONLY | Gtk.TextSearchFlags.TEXT_ONLY
|
|
||||||
if not self.use_case_sensitive:
|
|
||||||
flags = flags | Gtk.TextSearchFlags.CASE_INSENSITIVE
|
|
||||||
|
|
||||||
if self.search_only_in_selection and self._buffer.get_has_selection():
|
|
||||||
start_itr, limit = self._buffer.get_selection_bounds()
|
|
||||||
|
|
||||||
_results = []
|
|
||||||
while True:
|
|
||||||
result = start_itr.forward_search(query, flags, limit)
|
|
||||||
if not result: break
|
|
||||||
|
|
||||||
_results.append(result)
|
|
||||||
start_itr = result[1]
|
|
||||||
|
|
||||||
results = self.apply_filters(_results, query)
|
|
||||||
return results, len(results)
|
|
||||||
|
|
||||||
def apply_filters(self, _results, query):
|
|
||||||
results = []
|
|
||||||
for start, end in _results:
|
|
||||||
text = self._buffer.get_slice(start, end, include_hidden_chars = False)
|
|
||||||
if self.use_whole_word_search:
|
|
||||||
if not self.is_whole_word(start, end):
|
|
||||||
continue
|
|
||||||
|
|
||||||
results.append([start, end])
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def find_next(self, widget, eve = None, use_data = None):
|
|
||||||
mark = self._buffer.get_insert()
|
|
||||||
iter = self._buffer.get_iter_at_mark(mark)
|
|
||||||
iter.forward_line()
|
|
||||||
|
|
||||||
search_tag = self._tag_table.lookup(self.search_tag)
|
|
||||||
next_tag_found = iter.forward_to_tag_toggle(search_tag)
|
|
||||||
if not next_tag_found:
|
|
||||||
self._buffer.place_cursor( self._buffer.get_start_iter() )
|
|
||||||
mark = self._buffer.get_insert()
|
|
||||||
iter = self._buffer.get_iter_at_mark(mark)
|
|
||||||
iter.forward_to_tag_toggle(search_tag)
|
|
||||||
|
|
||||||
self._buffer.place_cursor(iter)
|
|
||||||
self._active_src_view.scroll_to_mark( self._buffer.get_insert(), 0.0, True, 0.0, 0.0 )
|
|
||||||
|
|
||||||
|
|
||||||
def find_all(self, widget):
|
|
||||||
...
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ReplaceMixin:
|
|
||||||
def replace(self, widget):
|
|
||||||
replace_text = self._replace_entry.get_text()
|
|
||||||
if self.find_text and replace_text:
|
|
||||||
self._buffer.begin_user_action()
|
|
||||||
|
|
||||||
iter = self._buffer.get_start_iter()
|
|
||||||
search_tag = self._tag_table.lookup(self.search_tag)
|
|
||||||
|
|
||||||
iter.forward_to_tag_toggle(search_tag)
|
|
||||||
self._do_replace(iter, replace_text)
|
|
||||||
self._active_src_view.scroll_to_iter( iter, 0.0, True, 0.0, 0.0 )
|
|
||||||
|
|
||||||
self._buffer.end_user_action()
|
|
||||||
|
|
||||||
def replace_all(self, widget):
|
|
||||||
replace_text = self._replace_entry.get_text()
|
|
||||||
if self.find_text:
|
|
||||||
self._buffer.begin_user_action()
|
|
||||||
|
|
||||||
mark = self._buffer.get_insert()
|
|
||||||
iter = self._buffer.get_start_iter()
|
|
||||||
search_tag = self._tag_table.lookup(self.search_tag)
|
|
||||||
|
|
||||||
while iter.forward_to_tag_toggle(search_tag):
|
|
||||||
self._do_replace(iter, replace_text)
|
|
||||||
iter = self._buffer.get_start_iter()
|
|
||||||
|
|
||||||
self._buffer.end_user_action()
|
|
||||||
|
|
||||||
|
|
||||||
def _do_replace(self, iter, text):
|
|
||||||
start, end = self.get_start_end(iter)
|
|
||||||
self.replace_in_buffer(start, end, text)
|
|
||||||
|
|
||||||
def replace_in_buffer(self, start, end, text):
|
|
||||||
pos_mark = self._buffer.create_mark("find-replace", end, True)
|
|
||||||
self._buffer.delete(start, end)
|
|
||||||
replace_iter = self._buffer.get_iter_at_mark(pos_mark)
|
|
||||||
self._buffer.insert(replace_iter, text)
|
|
||||||
|
|
||||||
def get_start_end(self, iter):
|
|
||||||
start = iter.copy()
|
|
||||||
end = None
|
|
||||||
|
|
||||||
while True:
|
|
||||||
iter.forward_char()
|
|
||||||
tags = iter.get_tags()
|
|
||||||
valid = False
|
|
||||||
for tag in tags:
|
|
||||||
if tag.props.name and self.search_tag in tag.props.name:
|
|
||||||
valid = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if valid:
|
|
||||||
continue
|
|
||||||
|
|
||||||
end = iter.copy()
|
|
||||||
break
|
|
||||||
|
|
||||||
return start, end
|
|
||||||
|
|
||||||
# NOTE: Below, lovingly taken from Hamad Al Marri's Gamma text editor.
|
|
||||||
# Link: https://gitlab.com/hamadmarri/gamma-text-editor
|
|
||||||
def is_whole_word(self, match_start, match_end):
|
|
||||||
is_prev_a_char = True
|
|
||||||
is_next_a_char = True
|
|
||||||
|
|
||||||
prev_iter = match_start.copy()
|
|
||||||
next_iter = match_end.copy()
|
|
||||||
if not prev_iter.backward_char():
|
|
||||||
is_prev_a_char = False
|
|
||||||
else:
|
|
||||||
c = prev_iter.get_char()
|
|
||||||
is_prev_a_char = (c.isalpha() or c.isdigit())
|
|
||||||
|
|
||||||
if not next_iter:
|
|
||||||
is_next_a_char = False
|
|
||||||
else:
|
|
||||||
c = next_iter.get_char()
|
|
||||||
is_next_a_char = (c.isalpha() or c.isdigit())
|
|
||||||
|
|
||||||
is_word = (not is_prev_a_char and not is_next_a_char)
|
|
||||||
|
|
||||||
# Note: Both must be false to be a word...
|
|
||||||
return is_word
|
|
||||||
@@ -1,299 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!-- Generated with glade 3.40.0 -->
|
|
||||||
<interface>
|
|
||||||
<requires lib="gtk+" version="3.24"/>
|
|
||||||
<object class="GtkImage" id="close_img">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="stock">gtk-close</property>
|
|
||||||
</object>
|
|
||||||
<object class="GtkImage" id="only-in-selection">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="pixbuf">../../icons/only-in-selection.png</property>
|
|
||||||
</object>
|
|
||||||
<object class="GtkImage" id="whole-word">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="pixbuf">../../icons/whole-word.png</property>
|
|
||||||
</object>
|
|
||||||
<object class="GtkPopover" id="search_replace_dialog">
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="modal">False</property>
|
|
||||||
<property name="transitions-enabled">False</property>
|
|
||||||
<property name="constrain-to">none</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="orientation">vertical</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="find_status_lbl">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="margin-start">5</property>
|
|
||||||
<property name="label" translatable="yes">Find in Current Buffer</property>
|
|
||||||
<property name="xalign">0</property>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="find_options_lbl">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="xpad">20</property>
|
|
||||||
<property name="label" translatable="yes">Finding with Options: Case Insensitive</property>
|
|
||||||
<property name="xalign">0</property>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButtonBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="layout-style">start</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkToggleButton">
|
|
||||||
<property name="label" translatable="yes">.*</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="sensitive">False</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="focus-on-click">False</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="tooltip-text" translatable="yes">Use Regex</property>
|
|
||||||
<signal name="toggled" handler="tggle_regex" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkToggleButton">
|
|
||||||
<property name="label" translatable="yes">Aa</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="focus-on-click">False</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="tooltip-text" translatable="yes">Match Case</property>
|
|
||||||
<signal name="toggled" handler="tggle_case_sensitive" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkToggleButton">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="focus-on-click">False</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="tooltip-text" translatable="yes">Only In Selection</property>
|
|
||||||
<property name="image">only-in-selection</property>
|
|
||||||
<property name="always-show-image">True</property>
|
|
||||||
<signal name="toggled" handler="tggle_selection_only_scan" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">2</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkToggleButton">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="focus-on-click">False</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="tooltip-text" translatable="yes">Whole Word</property>
|
|
||||||
<property name="image">whole-word</property>
|
|
||||||
<property name="always-show-image">True</property>
|
|
||||||
<signal name="toggled" handler="tggle_whole_word_search" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">3</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="focus-on-click">False</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="tooltip-text" translatable="yes">Close Panel</property>
|
|
||||||
<property name="image">close_img</property>
|
|
||||||
<property name="always-show-image">True</property>
|
|
||||||
<signal name="clicked" handler="_tggl_search_replace" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">True</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">4</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<!-- n-columns=10 n-rows=2 -->
|
|
||||||
<object class="GtkGrid">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">False</property>
|
|
||||||
<property name="column-homogeneous">True</property>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton">
|
|
||||||
<property name="label" translatable="yes">Replace All</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="focus-on-click">False</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="tooltip-text" translatable="yes">Replace All</property>
|
|
||||||
<property name="margin-start">5</property>
|
|
||||||
<property name="margin-end">5</property>
|
|
||||||
<property name="margin-top">5</property>
|
|
||||||
<property name="margin-bottom">5</property>
|
|
||||||
<signal name="clicked" handler="replace_all" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="left-attach">9</property>
|
|
||||||
<property name="top-attach">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton">
|
|
||||||
<property name="label" translatable="yes">Replace</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="focus-on-click">False</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="tooltip-text" translatable="yes">Replace Next</property>
|
|
||||||
<property name="margin-start">5</property>
|
|
||||||
<property name="margin-end">10</property>
|
|
||||||
<property name="margin-top">5</property>
|
|
||||||
<property name="margin-bottom">5</property>
|
|
||||||
<signal name="clicked" handler="replace" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="left-attach">8</property>
|
|
||||||
<property name="top-attach">1</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton">
|
|
||||||
<property name="label" translatable="yes">Find All</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="focus-on-click">False</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="margin-start">5</property>
|
|
||||||
<property name="margin-end">5</property>
|
|
||||||
<property name="margin-top">5</property>
|
|
||||||
<property name="margin-bottom">5</property>
|
|
||||||
<signal name="clicked" handler="find_all" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="left-attach">9</property>
|
|
||||||
<property name="top-attach">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkButton">
|
|
||||||
<property name="label" translatable="yes">Find</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="focus-on-click">False</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="margin-start">5</property>
|
|
||||||
<property name="margin-end">5</property>
|
|
||||||
<property name="margin-top">5</property>
|
|
||||||
<property name="margin-bottom">5</property>
|
|
||||||
<signal name="clicked" handler="find_next" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="left-attach">8</property>
|
|
||||||
<property name="top-attach">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSearchEntry" id="find_entry">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="primary-icon-name">edit-find-symbolic</property>
|
|
||||||
<property name="primary-icon-activatable">False</property>
|
|
||||||
<property name="primary-icon-sensitive">False</property>
|
|
||||||
<property name="placeholder-text" translatable="yes">Find in current buffer</property>
|
|
||||||
<signal name="key-release-event" handler="on_enter_search" swapped="no"/>
|
|
||||||
<signal name="search-changed" handler="search_for_string" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="left-attach">0</property>
|
|
||||||
<property name="top-attach">0</property>
|
|
||||||
<property name="width">8</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkSearchEntry" id="replace_entry">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="primary-icon-name">edit-find-symbolic</property>
|
|
||||||
<property name="primary-icon-activatable">False</property>
|
|
||||||
<property name="primary-icon-sensitive">False</property>
|
|
||||||
<property name="placeholder-text" translatable="yes">Replace in current buffer</property>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="left-attach">0</property>
|
|
||||||
<property name="top-attach">1</property>
|
|
||||||
<property name="width">8</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="padding">10</property>
|
|
||||||
<property name="position">3</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
</object>
|
|
||||||
</interface>
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class StylingMixin:
|
|
||||||
def tggle_regex(self, widget):
|
|
||||||
self.use_regex = not widget.get_active()
|
|
||||||
self._set_find_options_lbl()
|
|
||||||
self.search_for_string(self._find_entry)
|
|
||||||
|
|
||||||
def tggle_case_sensitive(self, widget):
|
|
||||||
self.use_case_sensitive = widget.get_active()
|
|
||||||
self._set_find_options_lbl()
|
|
||||||
self.search_for_string(self._find_entry)
|
|
||||||
|
|
||||||
def tggle_selection_only_scan(self, widget):
|
|
||||||
self.search_only_in_selection = widget.get_active()
|
|
||||||
self._set_find_options_lbl()
|
|
||||||
self.search_for_string(self._find_entry)
|
|
||||||
|
|
||||||
def tggle_whole_word_search(self, widget):
|
|
||||||
self.use_whole_word_search = widget.get_active()
|
|
||||||
self._set_find_options_lbl()
|
|
||||||
self.search_for_string(self._find_entry)
|
|
||||||
|
|
||||||
def _set_find_options_lbl(self):
|
|
||||||
find_options = "Finding with Options: "
|
|
||||||
|
|
||||||
if self.use_regex:
|
|
||||||
find_options += "Regex"
|
|
||||||
|
|
||||||
find_options += ", " if self.use_regex else ""
|
|
||||||
find_options += "Case Sensitive" if self.use_case_sensitive else "Case Inensitive"
|
|
||||||
|
|
||||||
if self.search_only_in_selection:
|
|
||||||
find_options += ", Within Current Selection"
|
|
||||||
|
|
||||||
if self.use_whole_word_search:
|
|
||||||
find_options += ", Whole Word"
|
|
||||||
|
|
||||||
self._find_options_lbl.set_label(find_options)
|
|
||||||
|
|
||||||
def update_style(self, state):
|
|
||||||
self._find_entry.get_style_context().remove_class("searching")
|
|
||||||
self._find_entry.get_style_context().remove_class("search_success")
|
|
||||||
self._find_entry.get_style_context().remove_class("search_fail")
|
|
||||||
|
|
||||||
if state == 0:
|
|
||||||
self._find_entry.get_style_context().add_class("searching")
|
|
||||||
elif state == 1:
|
|
||||||
self._find_entry.get_style_context().add_class("search_success")
|
|
||||||
elif state == 2:
|
|
||||||
self._find_entry.get_style_context().add_class("search_fail")
|
|
||||||
|
|
||||||
def _update_status_lbl(self, total_count: int = 0, query: str = None):
|
|
||||||
if not query: return
|
|
||||||
|
|
||||||
count = total_count if total_count > 0 else "No"
|
|
||||||
plural = "s" if total_count > 1 else ""
|
|
||||||
|
|
||||||
if total_count == 0: self.update_style(2)
|
|
||||||
self._find_status_lbl.set_label(f"{count} result{plural} found for '{query}'")
|
|
||||||
@@ -21,14 +21,14 @@ class Plugin(PluginUI):
|
|||||||
...
|
...
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
ui_element = self.requests_ui_element("plugin_control_list")
|
ui_element = self.requests_ui_element("header-container")
|
||||||
ui_element.add( self.generate_plugin_element() )
|
ui_element.add( self.generate_plugin_element() )
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
...
|
...
|
||||||
|
|
||||||
def generate_plugin_element(self):
|
def generate_plugin_element(self):
|
||||||
button = Gtk.Button(label = self.name)
|
button = Gtk.Button(label = "Hello, World!")
|
||||||
|
|
||||||
button.connect("button-release-event", self.send_message)
|
button.connect("button-release-event", self.send_message)
|
||||||
button.show()
|
button.show()
|
||||||
@@ -36,6 +36,5 @@ class Plugin(PluginUI):
|
|||||||
return button
|
return button
|
||||||
|
|
||||||
def send_message(self, widget = None, eve = None):
|
def send_message(self, widget = None, eve = None):
|
||||||
message = "Hello, World!"
|
logger.info("Hello, World!")
|
||||||
self.emit("display_message", ("warning", message, None))
|
|
||||||
|
|
||||||
@@ -68,6 +68,7 @@ builtins.call_chain = call_chain_wrapper
|
|||||||
# def custom_except_hook(exc_type, exc_value, exc_traceback):
|
# def custom_except_hook(exc_type, exc_value, exc_traceback):
|
||||||
# if issubclass(exc_type, KeyboardInterrupt):
|
# if issubclass(exc_type, KeyboardInterrupt):
|
||||||
# sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
# sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||||
|
# sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||||
# return
|
# return
|
||||||
|
|
||||||
# logger.error("Uncaught exception", exc_info = (exc_type, exc_value, exc_traceback))
|
# logger.error("Uncaught exception", exc_info = (exc_type, exc_value, exc_traceback))
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class FooterContainer(Gtk.Box):
|
|||||||
self.ctx = self.get_style_context()
|
self.ctx = self.get_style_context()
|
||||||
self.ctx.add_class("footer-container")
|
self.ctx.add_class("footer-container")
|
||||||
|
|
||||||
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||||
self.set_hexpand(True)
|
self.set_hexpand(True)
|
||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from libs.mixins.ipc_signals_mixin import IPCSignalsMixin
|
|||||||
from libs.mixins.keyboard_signals_mixin import KeyboardSignalsMixin
|
from libs.mixins.keyboard_signals_mixin import KeyboardSignalsMixin
|
||||||
|
|
||||||
from ..containers.base_container import BaseContainer
|
from ..containers.base_container import BaseContainer
|
||||||
|
from ..containers.code.code_container import CodeContainer
|
||||||
|
|
||||||
from .base_controller_mixin import BaseControllerMixin
|
from .base_controller_mixin import BaseControllerMixin
|
||||||
from .bridge_controller import BridgeController
|
from .bridge_controller import BridgeController
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ class CommandSystem:
|
|||||||
method = getattr(commands, command)
|
method = getattr(commands, command)
|
||||||
return method.execute(*args)
|
return method.execute(*args)
|
||||||
|
|
||||||
|
def add_command(self, command_name: str, command: callable):
|
||||||
|
setattr(commands, command_name, command)
|
||||||
|
|
||||||
|
|
||||||
def emit(self, event: Code_Event_Types.CodeEvent):
|
def emit(self, event: Code_Event_Types.CodeEvent):
|
||||||
""" Monkey patch 'emit' from command controller... """
|
""" Monkey patch 'emit' from command controller... """
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ __all__ = []
|
|||||||
|
|
||||||
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
|
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
|
||||||
module = importlib.import_module(f"{__name__}.{module_name}")
|
module = importlib.import_module(f"{__name__}.{module_name}")
|
||||||
globals()[module_name] = module # Add module to package namespace
|
# globals()[module_name] = module # Add module to package namespace
|
||||||
__all__.append(module_name)
|
__all__.append(module_name)
|
||||||
|
|
||||||
del pkgutil
|
del pkgutil
|
||||||
|
|||||||
@@ -18,12 +18,7 @@ def execute(
|
|||||||
uri: str
|
uri: str
|
||||||
):
|
):
|
||||||
logger.debug("Command: DnD Load File To Buffer")
|
logger.debug("Command: DnD Load File To Buffer")
|
||||||
|
file = view.command.new_file(view)
|
||||||
file = view.command.get_file(view)
|
|
||||||
buffer = file.buffer
|
|
||||||
|
|
||||||
if not file.ftype == "buffer":
|
|
||||||
file = view.command.new_file(view)
|
|
||||||
|
|
||||||
gfile = Gio.File.new_for_uri(uri)
|
gfile = Gio.File.new_for_uri(uri)
|
||||||
view.command.exec_with_args(
|
view.command.exec_with_args(
|
||||||
@@ -31,4 +26,5 @@ def execute(
|
|||||||
(view, gfile, file)
|
(view, gfile, file)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
view.set_buffer(file.buffer)
|
||||||
update_info_bar_if_focused(view.command, view)
|
update_info_bar_if_focused(view.command, view)
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ class CommandsController(ControllerBase, list):
|
|||||||
|
|
||||||
|
|
||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||||
if isinstance(event, Code_Event_Types.GetCommandSystemEvent):
|
if isinstance(event, Code_Event_Types.GetNewCommandSystemEvent):
|
||||||
event.response = self.get_command_system()
|
event.response = self.get_new_command_system()
|
||||||
|
|
||||||
def get_command_system(self):
|
def get_new_command_system(self):
|
||||||
command_system = CommandSystem()
|
command_system = CommandSystem()
|
||||||
command_system.emit = self.emit
|
command_system.emit = self.emit
|
||||||
command_system.emit_to = self.emit_to
|
command_system.emit_to = self.emit_to
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ class SourceViewsController(ControllerBase, list):
|
|||||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||||
if isinstance(event, Code_Event_Types.RemovedFileEvent):
|
if isinstance(event, Code_Event_Types.RemovedFileEvent):
|
||||||
self._remove_file(event)
|
self._remove_file(event)
|
||||||
|
elif isinstance(event, Code_Event_Types.RegisterCommandEvent):
|
||||||
|
self. _register_command(event)
|
||||||
|
|
||||||
if not self.signal_mapper.active_view: return
|
if not self.signal_mapper.active_view: return
|
||||||
|
|
||||||
@@ -40,8 +42,22 @@ class SourceViewsController(ControllerBase, list):
|
|||||||
elif isinstance(event, Code_Event_Types.TextInsertedEvent):
|
elif isinstance(event, Code_Event_Types.TextInsertedEvent):
|
||||||
self.signal_mapper.insert_text(event.file, event.text)
|
self.signal_mapper.insert_text(event.file, event.text)
|
||||||
|
|
||||||
|
def _register_command(self, event: Code_Event_Types.RegisterCommandEvent):
|
||||||
|
self.state_manager.key_mapper.map_command(
|
||||||
|
event.command_name,
|
||||||
|
{
|
||||||
|
f"{event.binding_mode}": event.binding
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
for view in self:
|
||||||
|
view.command.add_command(
|
||||||
|
event.command_name,
|
||||||
|
event.command
|
||||||
|
)
|
||||||
|
|
||||||
def _get_command_system(self):
|
def _get_command_system(self):
|
||||||
event = Event_Factory.create_event("get_command_system")
|
event = Event_Factory.create_event("get_new_command_system")
|
||||||
self.message_to("commands", event)
|
self.message_to("commands", event)
|
||||||
command = event.response
|
command = event.response
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from ...key_mapper import KeyMapper
|
|||||||
from .states import *
|
from .states import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SourceViewStateManager:
|
class SourceViewStateManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.key_mapper: KeyMapper = KeyMapper()
|
self.key_mapper: KeyMapper = KeyMapper()
|
||||||
|
|||||||
@@ -70,29 +70,31 @@ class KeyMapper:
|
|||||||
|
|
||||||
with open(bindings_file, 'r') as f:
|
with open(bindings_file, 'r') as f:
|
||||||
data = json.load(f)["keybindings"]
|
data = json.load(f)["keybindings"]
|
||||||
|
|
||||||
for command in data:
|
for command in data:
|
||||||
press_state = "held" if "held" in data[command] else "released"
|
self.map_command( command, data[command] )
|
||||||
keyname = data[command][press_state]
|
|
||||||
|
|
||||||
state = NoKeyState
|
|
||||||
if "<Control>" in keyname:
|
|
||||||
state = state | CtrlKeyState
|
|
||||||
if "<Shift>" in keyname:
|
|
||||||
state = state | ShiftKeyState
|
|
||||||
if "<Alt>" in keyname:
|
|
||||||
state = state | AltKeyState
|
|
||||||
|
|
||||||
keyname = keyname.replace("<Control>", "") \
|
|
||||||
.replace("<Shift>", "") \
|
|
||||||
.replace("<Alt>", "") \
|
|
||||||
.lower()
|
|
||||||
|
|
||||||
getattr(self.states[state], press_state)[keyname] = command
|
|
||||||
|
|
||||||
def re_map(self):
|
def re_map(self):
|
||||||
self.states = copy.deepcopy(self._map)
|
self.states = copy.deepcopy(self._map)
|
||||||
|
|
||||||
|
def map_command(self, command, entry):
|
||||||
|
press_state = "held" if "held" in entry else "released"
|
||||||
|
keyname = entry[press_state]
|
||||||
|
|
||||||
|
state = NoKeyState
|
||||||
|
if "<Control>" in keyname:
|
||||||
|
state = state | CtrlKeyState
|
||||||
|
if "<Shift>" in keyname:
|
||||||
|
state = state | ShiftKeyState
|
||||||
|
if "<Alt>" in keyname:
|
||||||
|
state = state | AltKeyState
|
||||||
|
|
||||||
|
keyname = keyname.replace("<Control>", "") \
|
||||||
|
.replace("<Shift>", "") \
|
||||||
|
.replace("<Alt>", "") \
|
||||||
|
.lower()
|
||||||
|
|
||||||
|
getattr(self.states[state], press_state)[keyname] = command
|
||||||
|
|
||||||
def _key_press_event(self, eve):
|
def _key_press_event(self, eve):
|
||||||
keyname = Gdk.keyval_name(eve.keyval).lower()
|
keyname = Gdk.keyval_name(eve.keyval).lower()
|
||||||
|
|
||||||
|
|||||||
@@ -13,9 +13,14 @@ class SourceBuffer(GtkSource.Buffer):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(SourceBuffer, self).__init__()
|
super(SourceBuffer, self).__init__()
|
||||||
|
|
||||||
|
self._handler_ids = []
|
||||||
self.is_processing_completion: bool = False
|
self.is_processing_completion: bool = False
|
||||||
|
|
||||||
self._handler_ids = []
|
self.create_tag(
|
||||||
|
"search-highlight",
|
||||||
|
background = "yellow",
|
||||||
|
foreground = "black"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def set_signals(
|
def set_signals(
|
||||||
|
|||||||
@@ -43,10 +43,26 @@ class SourceFile(GtkSource.File):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _changed(self, buffer: SourceBuffer):
|
def _changed(self, buffer: SourceBuffer):
|
||||||
|
self.check_file_on_disk()
|
||||||
|
|
||||||
event = Event_Factory.create_event("text_changed", buffer = buffer)
|
event = Event_Factory.create_event("text_changed", buffer = buffer)
|
||||||
event.file = self
|
event.file = self
|
||||||
self.emit(event)
|
self.emit(event)
|
||||||
|
|
||||||
|
if self.is_deleted():
|
||||||
|
print("deleted")
|
||||||
|
# event = Event_Factory.create_event("file_deleted", buffer = buffer)
|
||||||
|
# event.file = self
|
||||||
|
# self.emit(event)
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.is_externally_modified():
|
||||||
|
print("is_externally_modified")
|
||||||
|
# event = Event_Factory.create_event("file_externally_modified", buffer = buffer)
|
||||||
|
# event.file = self
|
||||||
|
# self.emit(event)
|
||||||
|
return
|
||||||
|
|
||||||
def _insert_text(
|
def _insert_text(
|
||||||
self,
|
self,
|
||||||
buffer: SourceBuffer,
|
buffer: SourceBuffer,
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ class SourceView(GtkSource.View, SourceViewDnDMixin):
|
|||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
self.connect("drag-data-received", self._on_drag_data_received)
|
self.connect("drag-data-received", self._on_drag_data_received)
|
||||||
|
self.connect("populate-popup", self._on_populate_popup)
|
||||||
|
|
||||||
def _subscribe_to_events(self):
|
def _subscribe_to_events(self):
|
||||||
...
|
...
|
||||||
@@ -76,6 +77,37 @@ class SourceView(GtkSource.View, SourceViewDnDMixin):
|
|||||||
|
|
||||||
self._set_up_dnd()
|
self._set_up_dnd()
|
||||||
|
|
||||||
|
def _on_populate_popup(self, view, menu):
|
||||||
|
buffer = self.get_buffer()
|
||||||
|
language = buffer.get_language()
|
||||||
|
|
||||||
|
if language.get_id() == "json":
|
||||||
|
self._load_pretify_json(view, menu)
|
||||||
|
|
||||||
|
menu.show_all()
|
||||||
|
|
||||||
|
def _load_prettify_json(self, view, menu):
|
||||||
|
menu.append( Gtk.SeparatorMenuItem() )
|
||||||
|
|
||||||
|
def on_prettify_json(menuitem):
|
||||||
|
import json
|
||||||
|
|
||||||
|
buffer = self.get_buffer()
|
||||||
|
start_itr, \
|
||||||
|
end_itr = buffer.get_start_iter(), buffer.get_end_iter()
|
||||||
|
data = buffer.get_text(start_itr, end_itr, False)
|
||||||
|
text = json.dumps(json.loads(data), separators = (',', ':'), indent = 4)
|
||||||
|
|
||||||
|
buffer.begin_user_action()
|
||||||
|
buffer.delete(start_itr, end_itr)
|
||||||
|
buffer.insert(start_itr, text)
|
||||||
|
buffer.end_user_action()
|
||||||
|
|
||||||
|
item = Gtk.MenuItem(label = "Prettify JSON")
|
||||||
|
item.connect("activate", on_prettify_json)
|
||||||
|
menu.append(item)
|
||||||
|
|
||||||
|
|
||||||
def clear_temp_cut_buffer_delayed(self):
|
def clear_temp_cut_buffer_delayed(self):
|
||||||
if self._cut_temp_timeout_id:
|
if self._cut_temp_timeout_id:
|
||||||
GLib.source_remove(self._cut_temp_timeout_id)
|
GLib.source_remove(self._cut_temp_timeout_id)
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class VteWidget(Vte.Terminal):
|
|||||||
ctx.add_class("vte-widget")
|
ctx.add_class("vte-widget")
|
||||||
|
|
||||||
self.set_clear_background(False)
|
self.set_clear_background(False)
|
||||||
|
self.set_hexpand(True)
|
||||||
self.set_enable_sixel(True)
|
self.set_enable_sixel(True)
|
||||||
self.set_cursor_shape( Vte.CursorShape.IBEAM )
|
self.set_cursor_shape( Vte.CursorShape.IBEAM )
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
|
|
||||||
from .code_event import CodeEvent
|
from .code_event import CodeEvent
|
||||||
from .register_provider_event import RegisterProviderEvent
|
from .register_provider_event import RegisterProviderEvent
|
||||||
|
from .register_command_event import RegisterCommandEvent
|
||||||
|
|
||||||
from .get_command_system_event import GetCommandSystemEvent
|
from .get_new_command_system_event import GetNewCommandSystemEvent
|
||||||
from .request_completion_event import RequestCompletionEvent
|
from .request_completion_event import RequestCompletionEvent
|
||||||
from .cursor_moved_event import CursorMovedEvent
|
from .cursor_moved_event import CursorMovedEvent
|
||||||
from .modified_changed_event import ModifiedChangedEvent
|
from .modified_changed_event import ModifiedChangedEvent
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ from .code_event import CodeEvent
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GetCommandSystemEvent(CodeEvent):
|
class GetNewCommandSystemEvent(CodeEvent):
|
||||||
...
|
...
|
||||||
20
src/libs/dto/code/register_command_event.py
Normal file
20
src/libs/dto/code/register_command_event.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Python imports
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('GtkSource', '4')
|
||||||
|
|
||||||
|
from gi.repository import GtkSource
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from ..base_event import BaseEvent
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RegisterCommandEvent(BaseEvent):
|
||||||
|
command_name: str = ""
|
||||||
|
command: callable = None
|
||||||
|
binding_mode: str = ""
|
||||||
|
binding: str = ""
|
||||||
@@ -15,11 +15,11 @@ from .dto import code
|
|||||||
|
|
||||||
class EventFactory(Singleton):
|
class EventFactory(Singleton):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self._event_classes: Dict[str, Type[BaseEvent]] = {}
|
self._event_classes: Dict[str, Type[BaseEvent]] = {}
|
||||||
|
|
||||||
self._auto_register_events( code.__dict__.items() )
|
self._auto_register_events( code.__dict__.items() )
|
||||||
|
|
||||||
|
|
||||||
def register_event(self, event_type: str, event_class: Type[BaseEvent]):
|
def register_event(self, event_type: str, event_class: Type[BaseEvent]):
|
||||||
self._event_classes[event_type] = event_class
|
self._event_classes[event_type] = event_class
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ class PluginCode(PluginBase):
|
|||||||
def run(self):
|
def run(self):
|
||||||
raise PluginCodeException("Plugin Code 'run' must be overriden by Plugin")
|
raise PluginCodeException("Plugin Code 'run' must be overriden by Plugin")
|
||||||
|
|
||||||
|
def requests_ui_element(self, element_id: str):
|
||||||
|
return self.plugin_context.requests_ui_element(element_id)
|
||||||
|
|
||||||
def message(self, event: BaseEvent):
|
def message(self, event: BaseEvent):
|
||||||
return self.plugin_context.message(event)
|
return self.plugin_context.message(event)
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,7 @@ class InvalidPluginException(Exception):
|
|||||||
class PluginsControllerMixin:
|
class PluginsControllerMixin:
|
||||||
|
|
||||||
def requests_ui_element(self, target_id: str):
|
def requests_ui_element(self, target_id: str):
|
||||||
builder = settings_manager.get_builder()
|
if not target_id in widget_registery.objects:
|
||||||
ui_target = builder.get_object(target_id)
|
raise InvalidPluginException('Unknown UI "target_id" given in requests.')
|
||||||
|
|
||||||
if not ui_target:
|
return widget_registery.objects[target_id]
|
||||||
raise InvalidPluginException('Unknown "target_id" given in requests.')
|
|
||||||
|
|
||||||
return ui_target
|
|
||||||
|
|||||||
Reference in New Issue
Block a user