diff --git a/plugins/lsp_completer/__init__.py b/plugins/completers/example_completer/__init__.py similarity index 100% rename from plugins/lsp_completer/__init__.py rename to plugins/completers/example_completer/__init__.py diff --git a/plugins/lsp_completer/__main__.py b/plugins/completers/example_completer/__main__.py similarity index 100% rename from plugins/lsp_completer/__main__.py rename to plugins/completers/example_completer/__main__.py diff --git a/plugins/completers/example_completer/manifest.json b/plugins/completers/example_completer/manifest.json new file mode 100644 index 0000000..e4c6f7a --- /dev/null +++ b/plugins/completers/example_completer/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Example Completer", + "author": "John Doe", + "version": "0.0.1", + "support": "", + "requests": {} +} diff --git a/plugins/completers/example_completer/plugin.py b/plugins/completers/example_completer/plugin.py new file mode 100644 index 0000000..f13331f --- /dev/null +++ b/plugins/completers/example_completer/plugin.py @@ -0,0 +1,40 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from libs.dto.base_event import BaseEvent +from libs.event_factory import Event_Factory + +from plugins.plugin_types import PluginCode + +from .provider import Provider + + + +class Plugin(PluginCode): + def __init__(self): + super(Plugin, self).__init__() + + self.provider: Provider = None + + + def _controller_message(self, event: BaseEvent): + ... + + def load(self): + self.provider = Provider() + + event = Event_Factory.create_event( + "register_provider", + provider_name = "Example Completer", + provider = self.provider, + language_ids = [] + ) + self.message_to("completion", event) + + def run(self): + ... diff --git a/plugins/completers/example_completer/provider.py b/plugins/completers/example_completer/provider.py new file mode 100644 index 0000000..a7d27bb --- /dev/null +++ b/plugins/completers/example_completer/provider.py @@ -0,0 +1,76 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('GtkSource', '4') + +from gi.repository import GObject +from gi.repository import GtkSource + +# Application imports +from .provider_response_cache import ProviderResponseCache + + + +class Provider(GObject.GObject, GtkSource.CompletionProvider): + """ + This is a custom Completion Example Provider. + # NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/ + """ + __gtype_name__ = 'ExampleCompletionProvider' + + def __init__(self): + super(Provider, self).__init__() + + self.response_cache: ProviderResponseCache = ProviderResponseCache() + + + def do_get_name(self): + """ Returns: a new string containing the name of the provider. """ + return 'Example Code Completion' + + def do_match(self, context): + # word = context.get_word() + # if not word or len(word) < 2: return False + + """ Get whether the provider match the context of completion detailed in context. """ + word = self.response_cache.get_word(context) + if not word or len(word) < 2: return False + + return True + + def do_get_priority(self): + """ Determin position in result list along other providor results. """ + return 5 + + def do_activate_proposal(self, proposal, iter_): + """ Manually handle actual completion insert or set flags and handle normally. """ + + buffer = iter_.get_buffer() + # Note: Flag mostly intended for SourceViewsMultiInsertState + # to insure marker processes inserted text correctly. + buffer.is_processing_completion = True + return False + + def do_get_activation(self): + """ The context for when a provider will show results """ + # return GtkSource.CompletionActivation.NONE + # return GtkSource.CompletionActivation.USER_REQUESTED + # return GtkSource.CompletionActivation.USER_REQUESTED | GtkSource.CompletionActivation.INTERACTIVE + return GtkSource.CompletionActivation.INTERACTIVE + + def do_populate(self, context): + results = self.response_cache.filter_with_context(context) + proposals = [] + + for entry in results: + proposals.append( + self.response_cache.create_completion_item( + entry["label"], + entry["text"], + entry["info"] + ) + ) + + context.add_proposals(self, proposals, True) + diff --git a/plugins/completers/example_completer/provider_response_cache.py b/plugins/completers/example_completer/provider_response_cache.py new file mode 100644 index 0000000..87af042 --- /dev/null +++ b/plugins/completers/example_completer/provider_response_cache.py @@ -0,0 +1,109 @@ +# Python imports +import re + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('GtkSource', '4') + +from gi.repository import Gtk +from gi.repository import GLib +from gi.repository import GtkSource + +# Application imports +from libs.event_factory import Code_Event_Types + +from core.widgets.code.completion_providers.provider_response_cache_base import ProviderResponseCacheBase + + + +class ProviderResponseCache(ProviderResponseCacheBase): + def __init__(self): + super(ProviderResponseCache, self).__init__() + + self.matchers: dict = { + "hello": { + "label": "Hello, World!", + "text": "Hello, World!", + "info": GLib.markup_escape_text( "Says the first ever program developers write..." ) + }, + "foo": { + "label": "foo", + "text": "foo }}" + }, + "bar": { + "label": "bar", + "text": "bar }}" + } + } + + + def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent): + ... + + def process_file_close(self, event: Code_Event_Types.RemovedFileEvent): + ... + + def process_file_save(self, event: Code_Event_Types.SavedFileEvent): + ... + + def process_file_change(self, event: Code_Event_Types.TextChangedEvent): + ... + + def filter(self, word: str) -> list[dict]: + ... + + def filter_with_context(self, context) -> list[dict]: + """ + In this instance, it will do 2 things: + 1) always provide Hello World! (Not ideal but an option so its in the example) + 2) Utilizes the Gtk.TextIter from the TextBuffer to determine if there is a jinja + example of '{{ custom.' if so it will provide you with the options of foo and bar. + If selected it will insert foo }} or bar }}, completing your syntax... + + PLEASE NOTE the GtkTextIter Logic and regex are really rough and should be adjusted and tuned + """ + + proposals: list[dict] = [ + { + "label": self.matchers[ "hello" ]["label"], + "text": self.matchers[ "hello" ]["text"], + "info": self.matchers[ "hello" ]["info"] + } + ] + + # Gtk Versions differ on get_iter responses... + end_iter = context.get_iter() + if not isinstance(end_iter, Gtk.TextIter): + _, end_iter = context.get_iter() + + if not end_iter: return + + buf = end_iter.get_buffer() + mov_iter = end_iter.copy() + if mov_iter.backward_search('{{', Gtk.TextSearchFlags.VISIBLE_ONLY): + mov_iter, _ = mov_iter.backward_search('{{', Gtk.TextSearchFlags.VISIBLE_ONLY) + left_text = buf.get_text(mov_iter, end_iter, True) + else: + left_text = '' + + if re.match(r'.*\{\{\s*custom\.$', left_text): + # optionally proposed based on left search via regex + proposals.append( + { + "label": self.matchers[ "foo" ]["label"], + "text": self.matchers[ "foo" ]["text"], + "info": "" + } + ) + + # optionally proposed based on left search via regex + proposals.append( + { + "label": self.matchers[ "bar" ]["label"], + "text": self.matchers[ "bar" ]["text"], + "info": "" + } + ) + + return proposals diff --git a/plugins/snippets_completer/__init__.py b/plugins/completers/lsp_completer/__init__.py similarity index 100% rename from plugins/snippets_completer/__init__.py rename to plugins/completers/lsp_completer/__init__.py diff --git a/plugins/snippets_completer/__main__.py b/plugins/completers/lsp_completer/__main__.py similarity index 100% rename from plugins/snippets_completer/__main__.py rename to plugins/completers/lsp_completer/__main__.py diff --git a/plugins/lsp_completer/manifest.json b/plugins/completers/lsp_completer/manifest.json similarity index 100% rename from plugins/lsp_completer/manifest.json rename to plugins/completers/lsp_completer/manifest.json diff --git a/plugins/lsp_completer/plugin.py b/plugins/completers/lsp_completer/plugin.py similarity index 100% rename from plugins/lsp_completer/plugin.py rename to plugins/completers/lsp_completer/plugin.py diff --git a/plugins/lsp_completer/provider.py b/plugins/completers/lsp_completer/provider.py similarity index 68% rename from plugins/lsp_completer/provider.py rename to plugins/completers/lsp_completer/provider.py index 305d057..b6de14d 100644 --- a/plugins/lsp_completer/provider.py +++ b/plugins/completers/lsp_completer/provider.py @@ -4,15 +4,15 @@ import gi gi.require_version('GtkSource', '4') -from gi.repository import GtkSource from gi.repository import GObject +from gi.repository import GtkSource # Application imports from .provider_response_cache import ProviderResponseCache -class Provider(GObject.Object, GtkSource.CompletionProvider): +class Provider(GObject.GObject, GtkSource.CompletionProvider): """ This code is an LSP code completion plugin for Newton. # NOTE: Some code pulled/referenced from here --> https://github.com/isamert/gedi @@ -20,7 +20,7 @@ class Provider(GObject.Object, GtkSource.CompletionProvider): __gtype_name__ = 'LSPProvider' def __init__(self): - GObject.Object.__init__(self) + super(Provider, self).__init__() self.response_cache: ProviderResponseCache = ProviderResponseCache() @@ -31,14 +31,11 @@ class Provider(GObject.Object, GtkSource.CompletionProvider): def do_get_name(self): return "LSP Code Completion" - def get_iter_correctly(self, context): - return context.get_iter()[1] if isinstance(context.get_iter(), tuple) else context.get_iter() - def do_match(self, context): word = self.response_cache.get_word(context) if not word or len(word) < 2: return False - iter = self.get_iter_correctly(context) + iter = self.response_cache.get_iter_correctly(context) iter.backward_char() ch = iter.get_char() # NOTE: Look to re-add or apply supprting logic to use spaces @@ -56,6 +53,13 @@ class Provider(GObject.Object, GtkSource.CompletionProvider): def do_get_priority(self): return 5 + def do_activate_proposal(self, proposal, iter_): + buffer = iter_.get_buffer() + # Note: Flag mostly intended for SourceViewsMultiInsertState + # to insure marker processes inserted text correctly. + buffer.is_processing_completion = True + return False + def do_get_activation(self): """ The context for when a provider will show results """ # return GtkSource.CompletionActivation.NONE @@ -63,17 +67,16 @@ class Provider(GObject.Object, GtkSource.CompletionProvider): # return GtkSource.CompletionActivation.INTERACTIVE def do_populate(self, context): - proposals = self.get_completion_filter(context) + results = self.response_cache.filter_with_context(context) + proposals = [] + + for entry in results: + proposals.append( + self.response_cache.create_completion_item( + entry["label"], + entry["text"], + entry["info"] + ) + ) context.add_proposals(self, proposals, True) - - def get_completion_filter(self, context): - proposals = [ - self.response_cache.create_completion_item( - "LSP Class", - "LSP Code", - "A test LSP completion item..." - ) - ] - - return proposals diff --git a/plugins/lsp_completer/provider_response_cache.py b/plugins/completers/lsp_completer/provider_response_cache.py similarity index 76% rename from plugins/lsp_completer/provider_response_cache.py rename to plugins/completers/lsp_completer/provider_response_cache.py index 54b827d..b1bf2d2 100644 --- a/plugins/lsp_completer/provider_response_cache.py +++ b/plugins/completers/lsp_completer/provider_response_cache.py @@ -30,16 +30,16 @@ class ProviderResponseCache(ProviderResponseCacheBase): def process_file_change(self, event: Code_Event_Types.TextChangedEvent): ... - def filter(self, word: str): - ... + def filter(self, word: str) -> list[dict]: + return [] - def filter_with_context(self, context: GtkSource.CompletionContext): + def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]: proposals = [ - self.create_completion_item( - "LSP Class", - "LSP Code", - "A test LSP completion item..." - ) + { + "label": "LSP Class", + "text": "LSP Code", + "info": "A test LSP completion item..." + } ] - return proposals + return proposals diff --git a/plugins/completers/snippets_completer/__init__.py b/plugins/completers/snippets_completer/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/completers/snippets_completer/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/completers/snippets_completer/__main__.py b/plugins/completers/snippets_completer/__main__.py new file mode 100644 index 0000000..a576329 --- /dev/null +++ b/plugins/completers/snippets_completer/__main__.py @@ -0,0 +1,3 @@ +""" + Pligin Package +""" diff --git a/plugins/snippets_completer/cson/__init__.py b/plugins/completers/snippets_completer/cson/__init__.py similarity index 100% rename from plugins/snippets_completer/cson/__init__.py rename to plugins/completers/snippets_completer/cson/__init__.py diff --git a/plugins/snippets_completer/cson/parser.py b/plugins/completers/snippets_completer/cson/parser.py similarity index 100% rename from plugins/snippets_completer/cson/parser.py rename to plugins/completers/snippets_completer/cson/parser.py diff --git a/plugins/snippets_completer/cson/speg/__init__.py b/plugins/completers/snippets_completer/cson/speg/__init__.py similarity index 100% rename from plugins/snippets_completer/cson/speg/__init__.py rename to plugins/completers/snippets_completer/cson/speg/__init__.py diff --git a/plugins/snippets_completer/cson/speg/peg.py b/plugins/completers/snippets_completer/cson/speg/peg.py similarity index 100% rename from plugins/snippets_completer/cson/speg/peg.py rename to plugins/completers/snippets_completer/cson/speg/peg.py diff --git a/plugins/snippets_completer/cson/writer.py b/plugins/completers/snippets_completer/cson/writer.py similarity index 100% rename from plugins/snippets_completer/cson/writer.py rename to plugins/completers/snippets_completer/cson/writer.py diff --git a/plugins/snippets_completer/manifest.json b/plugins/completers/snippets_completer/manifest.json similarity index 100% rename from plugins/snippets_completer/manifest.json rename to plugins/completers/snippets_completer/manifest.json diff --git a/plugins/snippets_completer/plugin.py b/plugins/completers/snippets_completer/plugin.py similarity index 100% rename from plugins/snippets_completer/plugin.py rename to plugins/completers/snippets_completer/plugin.py diff --git a/plugins/snippets_completer/provider.py b/plugins/completers/snippets_completer/provider.py similarity index 83% rename from plugins/snippets_completer/provider.py rename to plugins/completers/snippets_completer/provider.py index 9ae0c45..b7f0773 100644 --- a/plugins/snippets_completer/provider.py +++ b/plugins/completers/snippets_completer/provider.py @@ -3,11 +3,9 @@ import re # Lib imports import gi -gi.require_version('Gtk', '3.0') gi.require_version('GtkSource', '4') from gi.repository import GObject -from gi.repository import Gtk from gi.repository import GtkSource # Application imports @@ -23,7 +21,7 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider): __gtype_name__ = 'SnippetsCompletionProvider' def __init__(self): - GObject.Object.__init__(self) + super(Provider, self).__init__() self.response_cache: ProviderResponseCache = ProviderResponseCache() @@ -40,6 +38,13 @@ class Provider(GObject.GObject, GtkSource.CompletionProvider): def do_get_priority(self): return 2 + def do_activate_proposal(self, proposal, iter_): + buffer = iter_.get_buffer() + # Note: Flag mostly intended for SourceViewsMultiInsertState + # to insure marker processes inserted text correctly. + buffer.is_processing_completion = True + return False + def do_get_activation(self): """ The context for when a provider will show results """ # return GtkSource.CompletionActivation.NONE diff --git a/plugins/snippets_completer/provider_response_cache.py b/plugins/completers/snippets_completer/provider_response_cache.py similarity index 88% rename from plugins/snippets_completer/provider_response_cache.py rename to plugins/completers/snippets_completer/provider_response_cache.py index 656d703..7b0010c 100644 --- a/plugins/snippets_completer/provider_response_cache.py +++ b/plugins/completers/snippets_completer/provider_response_cache.py @@ -52,8 +52,8 @@ class ProviderResponseCache(ProviderResponseCacheBase): def process_file_change(self, event: Code_Event_Types.TextChangedEvent): ... - def filter(self, word: str): - response: list = [] + def filter(self, word: str) -> list[dict]: + response: list[dict] = [] for entry in self.matchers: if not word in entry: continue @@ -61,3 +61,8 @@ class ProviderResponseCache(ProviderResponseCacheBase): response.append(data) return response + + def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]: + response: list[dict] = [] + + return response diff --git a/plugins/snippets_completer/snippets.cson b/plugins/completers/snippets_completer/snippets.cson similarity index 100% rename from plugins/snippets_completer/snippets.cson rename to plugins/completers/snippets_completer/snippets.cson diff --git a/plugins/completers/words_completer/__init__.py b/plugins/completers/words_completer/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/completers/words_completer/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/completers/words_completer/__main__.py b/plugins/completers/words_completer/__main__.py new file mode 100644 index 0000000..a576329 --- /dev/null +++ b/plugins/completers/words_completer/__main__.py @@ -0,0 +1,3 @@ +""" + Pligin Package +""" diff --git a/plugins/completers/words_completer/alt_provider.py b/plugins/completers/words_completer/alt_provider.py new file mode 100644 index 0000000..f4ede69 --- /dev/null +++ b/plugins/completers/words_completer/alt_provider.py @@ -0,0 +1,53 @@ +# Python imports +import re + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('GtkSource', '4') + +from gi.repository import GObject +from gi.repository import Gtk +from gi.repository import GtkSource + +# Application imports +from .provider_response_cache import ProviderResponseCache + + + +class Provider(GtkSource.CompletionWords): + """ + This is a Words Completion Provider. + # NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/ + """ + __gtype_name__ = 'WordsCompletionProvider' + + def __init__(self): + super(Provider, self).__init__() + + self.response_cache: ProviderResponseCache = ProviderResponseCache() + + + def do_get_name(self): + return 'Words Completion' + + def do_match(self, context): + word = self.response_cache.get_word(context) + if not word or len(word) < 2: return False + return True + + def do_get_priority(self): + return 0 + + def do_activate_proposal(self, proposal, iter_): + buffer = iter_.get_buffer() + # Note: Flag mostly intended for SourceViewsMultiInsertState + # to insure marker processes inserted text correctly. + buffer.is_processing_completion = True + return False + + def do_get_activation(self): + """ The context for when a provider will show results """ + # return GtkSource.CompletionActivation.NONE + # return GtkSource.CompletionActivation.USER_REQUESTED + return GtkSource.CompletionActivation.INTERACTIVE diff --git a/plugins/completers/words_completer/manifest.json b/plugins/completers/words_completer/manifest.json new file mode 100644 index 0000000..2ec483d --- /dev/null +++ b/plugins/completers/words_completer/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Words Completer", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": {} +} diff --git a/plugins/completers/words_completer/plugin.py b/plugins/completers/words_completer/plugin.py new file mode 100644 index 0000000..abb4acf --- /dev/null +++ b/plugins/completers/words_completer/plugin.py @@ -0,0 +1,40 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from libs.dto.base_event import BaseEvent +from libs.event_factory import Event_Factory + +from plugins.plugin_types import PluginCode + +from .provider import Provider + + + +class Plugin(PluginCode): + def __init__(self): + super(Plugin, self).__init__() + + self.provider: Provider = None + + + def _controller_message(self, event: BaseEvent): + ... + + def load(self): + self.provider = Provider() + + event = Event_Factory.create_event( + "register_provider", + provider_name = "Words Completer", + provider = self.provider, + language_ids = [] + ) + self.message_to("completion", event) + + def run(self): + ... diff --git a/plugins/completers/words_completer/provider.py b/plugins/completers/words_completer/provider.py new file mode 100644 index 0000000..b0d6bbc --- /dev/null +++ b/plugins/completers/words_completer/provider.py @@ -0,0 +1,71 @@ +# Python imports +import re + +# Lib imports +import gi +gi.require_version('GtkSource', '4') + +from gi.repository import GObject +from gi.repository import GtkSource + +# Application imports +from .provider_response_cache import ProviderResponseCache + + + +class Provider(GObject.GObject, GtkSource.CompletionProvider): + """ + This is a Words Completion Provider. + # NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/ + """ + # __gtype_name__ = 'WordsCompletionProvider' + + def __init__(self): + super(Provider, self).__init__() + + self.response_cache: ProviderResponseCache = ProviderResponseCache() + + + def do_get_name(self): + return 'Words Completion' + + def do_match(self, context): + word = self.response_cache.get_word(context) + if not word or len(word) < 2: return False + return True + + def do_get_priority(self): + return 0 + + def do_activate_proposal(self, proposal, iter_): + buffer = iter_.get_buffer() + # Note: Flag mostly intended for SourceViewsMultiInsertState + # to insure marker processes inserted text correctly. + buffer.is_processing_completion = True + return False + + def do_get_activation(self): + """ The context for when a provider will show results """ + # return GtkSource.CompletionActivation.NONE + # return GtkSource.CompletionActivation.USER_REQUESTED + return GtkSource.CompletionActivation.INTERACTIVE + + def do_populate(self, context): + word = self.response_cache.get_word(context) + results = self.response_cache.filter_with_context(context) + # results = self.response_cache.filter(word) + + # if not results: + # results = self.response_cache.filter_with_context(context) + + proposals = [] + for entry in results: + proposals.append( + self.response_cache.create_completion_item( + entry["label"], + entry["text"], + entry["info"] + ) + ) + + context.add_proposals(self, proposals, True) diff --git a/plugins/completers/words_completer/provider_response_cache.py b/plugins/completers/words_completer/provider_response_cache.py new file mode 100644 index 0000000..4194c28 --- /dev/null +++ b/plugins/completers/words_completer/provider_response_cache.py @@ -0,0 +1,131 @@ +# Python imports +from os import path + +# Lib imports +import gi +gi.require_version('GtkSource', '4') + +from gi.repository import GLib +from gi.repository import GtkSource + +# Application imports +from libs.event_factory import Code_Event_Types + +from core.widgets.code.completion_providers.provider_response_cache_base import ProviderResponseCacheBase + + + +class ProviderResponseCache(ProviderResponseCacheBase): + def __init__(self): + super(ProviderResponseCache, self).__init__() + + self.matchers: dict = {} + + + def process_file_load(self, event: Code_Event_Types.AddedNewFileEvent): + self.load_as_new_set(event.file.buffer) + + def process_file_close(self, event: Code_Event_Types.RemovedFileEvent): + self.matchers[event.file.buffer] = [] + del self.matchers[event.file.buffer] + + def process_file_save(self, event: Code_Event_Types.SavedFileEvent): + ... + + def process_file_change(self, event: Code_Event_Types.TextChangedEvent): + buffer = event.file.buffer + # if self.get_if_in_matched_word_set(buffer): return + self.load_as_new_set(buffer) + + + def filter(self, word: str) -> list[dict]: + response: list[dict] = [] + + for entry in self.matchers: + if not word in entry: continue + data = self.matchers[entry] + response.append(data) + + return response + + def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]: + buffer = self.get_iter_correctly(context).get_buffer() + word = self.get_word(context).rstrip() + + response: list[dict] = [] + for entry in self.matchers[buffer]: + if not entry.rstrip().startswith(word): continue + + data = { + "label": entry, + "text": entry, + "info": "" + } + + response.append(data) + + return response + + + def load_as_new_set(self, buffer): + start_itr = buffer.get_start_iter() + end_itr = buffer.get_end_iter() + data = buffer.get_text(start_itr, end_itr, False) + + if not data: + self.matchers[buffer] = set() + return + + self.matchers[buffer] = self.get_all_words(data) + + def get_if_in_matched_word_set(self, buffer): + was_found = False + + if not buffer in self.matchers: return was_found + + insert_itr = buffer.get_iter_at_mark( buffer.get_insert() ) + end_itr = insert_itr.copy() + start_itr = end_itr.copy() + + if not start_itr.starts_word(): + start_itr.backward_word_start() + + if not end_itr.ends_word(): + end_itr.forward_word_end() + + word = buffer.get_text(start_itr, end_itr, False) + for _word in self.matchers[buffer]: + if not _word.startswith(word): continue + was_found = True + + if was_found: return was_found + + self.matchers[buffer].add(word) + + return was_found + + + def get_all_words(self, data: str): + words = set() + + def is_word_char(c): + return c.isalnum() or c == '_' + + size = len(data) + i = 0 + + while i < size: + # Skip non-word characters + while i < size and not is_word_char(data[i]): + i += 1 + + start = i + # Consume word characters + while i < size and is_word_char(data[i]): + i += 1 + + word = data[start:i] + if not word: continue + words.add(word) + + return words diff --git a/src/core/widgets/code/completion_providers/provider_response_cache_base.py b/src/core/widgets/code/completion_providers/provider_response_cache_base.py index 0ecce4d..568d29a 100644 --- a/src/core/widgets/code/completion_providers/provider_response_cache_base.py +++ b/src/core/widgets/code/completion_providers/provider_response_cache_base.py @@ -51,7 +51,7 @@ class ProviderResponseCacheBase: text: str = "", info: str = "", completion: any = None - ): + ) -> dict: if not label or not text: return comp_item = GtkSource.CompletionItem.new() @@ -69,19 +69,51 @@ class ProviderResponseCacheBase: return comp_item - def get_word(self, context): - start_iter = context.get_iter() - end_iter = None + def get_all_marks(self, buffer): + marks = [] + iter_ = buffer.get_start_iter() - if not isinstance(start_iter, Gtk.TextIter): - _, start_iter = context.get_iter() - end_iter = start_iter.copy() + while iter_: + marks = iter_.get_marks() + + for mark in marks: + if mark and mark not in marks: + marks.append(mark) + + if not iter_.forward_char(): + break + + return marks + + def get_all_insert_marks(self, buffer): + marks = [] + iter_ = buffer.get_start_iter() + + while iter_: + marks = iter_.get_marks() + + for mark in marks: + if mark.get_name() and "multi_insert_" in mark.get_name(): + marks.append(mark) + + if not iter_.forward_char(): + break + + return marks + + def get_word(self, context) -> str: + start_iter = self.get_iter_correctly(context) + end_iter = start_iter.copy() if not start_iter.starts_word(): start_iter.backward_word_start() - end_iter.forward_word_end() + if not end_iter.ends_word(): + end_iter.forward_word_end() buffer = start_iter.get_buffer() return buffer.get_text(start_iter, end_iter, False) + + def get_iter_correctly(self, context): + return context.get_iter()[1] if isinstance(context.get_iter(), tuple) else context.get_iter() diff --git a/src/core/widgets/code/controllers/completion_controller.py b/src/core/widgets/code/controllers/completion_controller.py index 91dec63..f0205e1 100644 --- a/src/core/widgets/code/controllers/completion_controller.py +++ b/src/core/widgets/code/controllers/completion_controller.py @@ -45,10 +45,13 @@ class CompletionController(ControllerBase): def register_completer(self, completer: GtkSource.Completion): self._completers.append(completer) - completer.add_provider(self.words_provider) + # completer.add_provider(self.words_provider) for provider in self._providers.values(): completer.add_provider(provider) + def unregister_completer(self, completer: GtkSource.Completion): + self._completers.remove(completer) + def register_provider( self, provider_name: str, diff --git a/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py b/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py index 8de34be..689954f 100644 --- a/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py +++ b/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py @@ -34,6 +34,9 @@ class SourceViewsMultiInsertState(MarkEventsMixin): if not self.insert_markers: return False buffer = file.buffer + if buffer.is_processing_completion: + self.insert_completion_text(buffer, text) + return True # freeze buffer and insert to each mark (if any) buffer.block_insert_after_signal() @@ -49,6 +52,32 @@ class SourceViewsMultiInsertState(MarkEventsMixin): return True + def insert_completion_text(self, buffer, text): + buffer.is_processing_completion = False + + # freeze buffer and insert to each mark (if any) + buffer.block_insert_after_signal() + buffer.begin_user_action() + + with buffer.freeze_notify(): + for mark in self.insert_markers: + end_itr = buffer.get_iter_at_mark(mark) + start_itr = end_itr.copy() + + if not start_itr.starts_word(): + start_itr.backward_word_start() + + if not end_itr.ends_word(): + end_itr.forward_word_end() + + buffer.delete(start_itr, end_itr) + buffer.insert(end_itr, text, -1) + + buffer.end_user_action() + buffer.unblock_insert_after_signal() + + return True + def move_cursor(self, source_view, step, count, extend_selection, emit): buffer = source_view.get_buffer() @@ -95,7 +124,7 @@ class SourceViewsMultiInsertState(MarkEventsMixin): return False is_future = key_mapper._key_release_event(eve) - if is_future: return False + if is_future: return True command = key_mapper._key_press_event(eve) if not command: return False @@ -108,7 +137,7 @@ class SourceViewsMultiInsertState(MarkEventsMixin): command = key_mapper._key_release_event(eve) is_past = key_mapper._key_press_event(eve) - if is_past: return False + if is_past: return True if not command: return False source_view.command.exec(command) diff --git a/src/core/widgets/code/source_buffer.py b/src/core/widgets/code/source_buffer.py index 083d15c..8751128 100644 --- a/src/core/widgets/code/source_buffer.py +++ b/src/core/widgets/code/source_buffer.py @@ -13,6 +13,8 @@ class SourceBuffer(GtkSource.Buffer): def __init__(self): super(SourceBuffer, self).__init__() + self.is_processing_completion: bool = False + self._handler_ids = []