Compare commits

..

13 Commits

Author SHA1 Message Date
6c42ff7c7d Made words completer run async on load and update; swapped out tabs to use Gtk.Notebook 2026-02-16 17:11:30 -06:00
b922415f98 Moved completers to new dir; improved completers and added word completion 2026-02-16 01:39:23 -06:00
5273c58ed6 Major completion provider overhaul; pluigin load and pattern improvements; css overhaul/cleanup; source view state modes added 2026-02-14 16:15:54 -06:00
4ce4d85842 removed endpoint_registery; relocated builder_wrapper to be widget_registery under libs/ 2026-01-19 13:57:44 -06:00
c6b10ceb17 defaulting to buffer if language not detected 2026-01-19 00:29:10 -06:00
a036dc428b Wiring plugins to controller messages; importing plugin controller to code base; fixed VTE widget adding to bash history 2026-01-18 22:39:52 -06:00
b8ce6e160a Pligins refactor with new context and controller integration 2026-01-18 20:36:17 -06:00
e2f29207ba Preliminary controller layout work for plugin integration; controller base message refactor naming 2026-01-18 13:53:29 -06:00
99f1bffefb Restructuring controller to broaden usability; changed events logic to support that 2026-01-18 00:52:54 -06:00
f7d944f7a9 Created libs.code package and moved pertinant DTOs to it as well as widget.code that can go there too 2026-01-13 11:23:26 -06:00
4469e8189f Improved folder structure for commamd system and controllers 2026-01-13 00:43:51 -06:00
a507469bf8 Added controller manager registery guard; localized event type access for code to event factory 2026-01-12 23:59:13 -06:00
a07123d165 fix singleton typeing and __init__ 2026-01-12 23:34:34 -06:00
156 changed files with 4048 additions and 1498 deletions

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
{
"name": "Example Completer",
"author": "John Doe",
"version": "0.0.1",
"support": "",
"requests": {}
}

View File

@@ -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):
...

View File

@@ -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)

View File

@@ -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( "<b>Says the first ever program developers write...</b>" )
},
"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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,43 @@
# 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 = "LSP Completer",
provider = self.provider,
language_ids = []
)
self.message_to("completion", event)
def run(self):
...
def generate_plugin_element(self):
...

View File

@@ -0,0 +1,82 @@
# 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 code is an LSP code completion plugin for Newton.
# NOTE: Some code pulled/referenced from here --> https://github.com/isamert/gedi
"""
__gtype_name__ = 'LSPProvider'
def __init__(self):
super(Provider, self).__init__()
self.response_cache: ProviderResponseCache = ProviderResponseCache()
def pre_populate(self, context):
...
def do_get_name(self):
return "LSP Code Completion"
def do_match(self, context):
word = self.response_cache.get_word(context)
if not word or len(word) < 2: return False
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
# As is it slows down the editor in certain contexts...
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
if not (ch in ('_', '.') or ch.isalnum()):
return False
buffer = iter.get_buffer()
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
return False
return True
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
return GtkSource.CompletionActivation.USER_REQUESTED
# 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)

View File

@@ -0,0 +1,45 @@
# Python imports
# Lib imports
import gi
gi.require_version('GtkSource', '4')
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__()
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]:
return []
def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]:
proposals = [
{
"label": "LSP Class",
"text": "LSP Code",
"info": "A test LSP completion item..."
}
]
return proposals

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
from .parser import load, loads
from .writer import dump, dumps
from .speg import ParseError

View File

@@ -0,0 +1,295 @@
from .speg import peg
import re, sys
if sys.version_info[0] == 2:
_chr = unichr
else:
_chr = chr
def load(fin):
return loads(fin.read())
def loads(s):
if isinstance(s, bytes):
s = s.decode('utf-8')
if s.startswith(u'\ufeff'):
s = s[1:]
return peg(s.replace('\r\n', '\n'), _p_root)
def _p_ws(p):
p('[ \t]*')
def _p_nl(p):
p(r'([ \t]*(?:#[^\n]*)?\r?\n)+')
def _p_ews(p):
with p:
p(_p_nl)
p(_p_ws)
def _p_id(p):
return p(r'[$a-zA-Z_][$0-9a-zA-Z_]*')
_escape_table = {
'r': '\r',
'n': '\n',
't': '\t',
'f': '\f',
'b': '\b',
}
def _p_unescape(p):
esc = p('\\\\(?:u[0-9a-fA-F]{4}|[^\n])')
if esc[1] == 'u':
return _chr(int(esc[2:], 16))
return _escape_table.get(esc[1:], esc[1:])
_re_indent = re.compile(r'[ \t]*')
def _p_block_str(p, c):
p(r'{c}{c}{c}'.format(c=c))
lines = [['']]
with p:
while True:
s = p(r'(?:{c}(?!{c}{c})|[^{c}\\])*'.format(c=c))
l = s.split('\n')
lines[-1].append(l[0])
lines.extend([x] for x in l[1:])
if p(r'(?:\\\n[ \t]*)*'):
continue
p.commit()
lines[-1].append(p(_p_unescape))
p(r'{c}{c}{c}'.format(c=c))
lines = [''.join(l) for l in lines]
strip_ws = len(lines) > 1
if strip_ws and all(c in ' \t' for c in lines[-1]):
lines.pop()
indent = None
for line in lines[1:]:
if not line:
continue
if indent is None:
indent = _re_indent.match(line).group(0)
continue
for i, (c1, c2) in enumerate(zip(indent, line)):
if c1 != c2:
indent = indent[:i]
break
ind_len = len(indent or '')
if strip_ws and all(c in ' \t' for c in lines[0]):
lines = [line[ind_len:] for line in lines[1:]]
else:
lines[1:] = [line[ind_len:] for line in lines[1:]]
return '\n'.join(lines)
_re_mstr_nl = re.compile(r'(?:[ \t]*\n)+[ \t]*')
_re_mstr_trailing_nl = re.compile(_re_mstr_nl.pattern + r'\Z')
def _p_multiline_str(p, c):
p('{c}(?!{c}{c})(?:[ \t]*\n[ \t]*)?'.format(c=c))
string_parts = []
with p:
while True:
string_parts.append(p(r'[^{c}\\]*'.format(c=c)))
if p(r'(?:\\\n[ \t]*)*'):
string_parts.append('')
continue
p.commit()
string_parts.append(p(_p_unescape))
p(c)
string_parts[-1] = _re_mstr_trailing_nl.sub('', string_parts[-1])
string_parts[::2] = [_re_mstr_nl.sub(' ', part) for part in string_parts[::2]]
return ''.join(string_parts)
def _p_string(p):
with p:
return p(_p_block_str, '"')
with p:
return p(_p_block_str, "'")
with p:
return p(_p_multiline_str, '"')
return p(_p_multiline_str, "'")
def _p_array_value(p):
with p:
p(_p_nl)
return p(_p_object)
with p:
p(_p_ws)
return p(_p_line_object)
p(_p_ews)
return p(_p_simple_value)
def _p_key(p):
with p:
return p(_p_id)
return p(_p_string)
def _p_flow_kv(p):
k = p(_p_key)
p(_p_ews)
p(':')
with p:
p(_p_nl)
return k, p(_p_object)
with p:
p(_p_ws)
return k, p(_p_line_object)
p(_p_ews)
return k, p(_p_simple_value)
def _p_flow_obj_sep(p):
with p:
p(_p_ews)
p(',')
p(_p_ews)
return
p(_p_nl)
p(_p_ws)
def _p_simple_value(p):
with p:
p('null')
return None
with p:
p('false')
return False
with p:
p('true')
return True
with p:
return int(p('0b[01]+')[2:], 2)
with p:
return int(p('0o[0-7]+')[2:], 8)
with p:
return int(p('0x[0-9a-fA-F]+')[2:], 16)
with p:
return float(p(r'-?(?:[1-9][0-9]*|0)?\.[0-9]+(?:[Ee][\+-]?[0-9]+)?|(?:[1-9][0-9]*|0)(?:\.[0-9]+)?[Ee][\+-]?[0-9]+'))
with p:
return int(p('-?[1-9][0-9]*|0'), 10)
with p:
return p(_p_string)
with p:
p(r'\[')
r = []
with p:
p.set('I', '')
r.append(p(_p_array_value))
with p:
while True:
with p:
p(_p_ews)
p(',')
rr = p(_p_array_value)
if not p:
p(_p_nl)
with p:
rr = p(_p_object)
if not p:
p(_p_ews)
rr = p(_p_simple_value)
r.append(rr)
p.commit()
with p:
p(_p_ews)
p(',')
p(_p_ews)
p(r'\]')
return r
p(r'\{')
r = {}
p(_p_ews)
with p:
p.set('I', '')
k, v = p(_p_flow_kv)
r[k] = v
with p:
while True:
p(_p_flow_obj_sep)
k, v = p(_p_flow_kv)
r[k] = v
p.commit()
p(_p_ews)
with p:
p(',')
p(_p_ews)
p(r'\}')
return r
def _p_line_kv(p):
k = p(_p_key)
p(_p_ws)
p(':')
p(_p_ws)
with p:
p(_p_nl)
p(p.get('I'))
return k, p(_p_indented_object)
with p:
return k, p(_p_line_object)
with p:
return k, p(_p_simple_value)
p(_p_nl)
p(p.get('I'))
p('[ \t]')
p(_p_ws)
return k, p(_p_simple_value)
def _p_line_object(p):
k, v = p(_p_line_kv)
r = { k: v }
with p:
while True:
p(_p_ws)
p(',')
p(_p_ws)
k, v = p(_p_line_kv)
r[k] = v # uniqueness
p.commit()
return r
def _p_object(p):
p.set('I', p.get('I') + p('[ \t]*'))
r = p(_p_line_object)
with p:
while True:
p(_p_ws)
with p:
p(',')
p(_p_nl)
p(p.get('I'))
rr = p(_p_line_object)
r.update(rr) # unqueness
p.commit()
return r
def _p_indented_object(p):
p.set('I', p.get('I') + p('[ \t]'))
return p(_p_object)
def _p_root(p):
with p:
p(_p_nl)
with p:
p.set('I', '')
r = p(_p_object)
p(_p_ws)
with p:
p(',')
if not p:
p(_p_ws)
r = p(_p_simple_value)
p(_p_ews)
p(p.eof)
return r

View File

@@ -0,0 +1 @@
from .peg import peg, ParseError

View File

@@ -0,0 +1,147 @@
import sys, re
class ParseError(Exception):
def __init__(self, msg, text, offset, line, col):
self.msg = msg
self.text = text
self.offset = offset
self.line = line
self.col = col
super(ParseError, self).__init__(msg, offset, line, col)
if sys.version_info[0] == 2:
_basestr = basestring
else:
_basestr = str
def peg(s, r):
p = _Peg(s)
try:
return p(r)
except _UnexpectedError as e:
offset = max(p._errors)
err = p._errors[offset]
raise ParseError(err.msg, s, offset, err.line, err.col)
class _UnexpectedError(RuntimeError):
def __init__(self, state, expr):
self.state = state
self.expr = expr
class _PegState:
def __init__(self, pos, line, col):
self.pos = pos
self.line = line
self.col = col
self.vars = {}
self.commited = False
class _PegError:
def __init__(self, msg, line, col):
self.msg = msg
self.line = line
self.col = col
class _Peg:
def __init__(self, s):
self._s = s
self._states = [_PegState(0, 1, 1)]
self._errors = {}
self._re_cache = {}
def __call__(self, r, *args, **kw):
if isinstance(r, _basestr):
compiled = self._re_cache.get(r)
if not compiled:
compiled = re.compile(r)
self._re_cache[r] = compiled
st = self._states[-1]
m = compiled.match(self._s[st.pos:])
if not m:
self.error(expr=r, err=kw.get('err'))
ms = m.group(0)
st.pos += len(ms)
nl_pos = ms.rfind('\n')
if nl_pos < 0:
st.col += len(ms)
else:
st.col = len(ms) - nl_pos
st.line += ms[:nl_pos].count('\n') + 1
return ms
else:
kw.pop('err', None)
return r(self, *args, **kw)
def __repr__(self):
pos = self._states[-1].pos
vars = {}
for st in self._states:
vars.update(st.vars)
return '_Peg(%r, %r)' % (self._s[:pos] + '*' + self._s[pos:], vars)
@staticmethod
def eof(p):
if p._states[-1].pos != len(p._s):
p.error()
def error(self, err=None, expr=None):
st = self._states[-1]
if err is None:
err = 'expected {!r}, found {!r}'.format(expr, self._s[st.pos:st.pos+4])
self._errors[st.pos] = _PegError(err, st.line, st.col)
raise _UnexpectedError(st, expr)
def get(self, key, default=None):
for state in self._states[::-1]:
if key in state.vars:
return state.vars[key][0]
return default
def set(self, key, value):
self._states[-1].vars[key] = value, False
def set_global(self, key, value):
self._states[-1].vars[key] = value, True
def opt(self, *args, **kw):
with self:
return self(*args, **kw)
def not_(self, s, *args, **kw):
with self:
self(s)
self.error()
def __enter__(self):
self._states[-1].committed = False
self._states.append(_PegState(self._states[-1].pos, self._states[-1].line, self._states[-1].col))
def __exit__(self, type, value, traceback):
if type is None:
self.commit()
self._states.pop()
return type == _UnexpectedError
def commit(self):
cur = self._states[-1]
prev = self._states[-2]
for key in cur.vars:
val, g = cur.vars[key]
if not g:
continue
if key in prev.vars:
prev.vars[key] = val, prev.vars[key][1]
else:
prev.vars[key] = val, True
prev.pos = cur.pos
prev.line = cur.line
prev.col = cur.col
prev.committed = True
def __nonzero__(self):
return self._states[-1].committed
__bool__ = __nonzero__

View File

@@ -0,0 +1,191 @@
import re, json, sys
if sys.version_info[0] == 2:
def _is_num(o):
return isinstance(o, int) or isinstance(o, long) or isinstance(o, float)
def _stringify(o):
if isinstance(o, str):
return unicode(o)
if isinstance(o, unicode):
return o
return None
else:
def _is_num(o):
return isinstance(o, int) or isinstance(o, float)
def _stringify(o):
if isinstance(o, bytes):
return o.decode()
if isinstance(o, str):
return o
return None
_id_re = re.compile(r'[$a-zA-Z_][$0-9a-zA-Z_]*\Z')
class CSONEncoder:
def __init__(self, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False,
indent=None, default=None):
self._skipkeys = skipkeys
self._ensure_ascii = ensure_ascii
self._allow_nan = allow_nan
self._sort_keys = sort_keys
self._indent = ' ' * (indent or 4)
self._default = default
if check_circular:
self._obj_stack = set()
else:
self._obj_stack = None
def _format_simple_val(self, o):
if o is None:
return 'null'
if isinstance(o, bool):
return 'true' if o else 'false'
if _is_num(o):
return str(o)
s = _stringify(o)
if s is not None:
return self._escape_string(s)
return None
def _escape_string(self, s):
r = json.dumps(s, ensure_ascii=self._ensure_ascii)
return u"'{}'".format(r[1:-1].replace("'", r"\'"))
def _escape_key(self, s):
if s is None or isinstance(s, bool) or _is_num(s):
s = str(s)
s = _stringify(s)
if s is None:
if self._skipkeys:
return None
raise TypeError('keys must be a string')
if not _id_re.match(s):
return self._escape_string(s)
return s
def _push_obj(self, o):
if self._obj_stack is not None:
if id(o) in self._obj_stack:
raise ValueError('Circular reference detected')
self._obj_stack.add(id(o))
def _pop_obj(self, o):
if self._obj_stack is not None:
self._obj_stack.remove(id(o))
def _encode(self, o, obj_val=False, indent='', force_flow=False):
if isinstance(o, list):
if not o:
if obj_val:
yield ' []\n'
else:
yield indent
yield '[]\n'
else:
if obj_val:
yield ' [\n'
else:
yield indent
yield '[\n'
indent = indent + self._indent
self._push_obj(o)
for v in o:
for chunk in self._encode(v, obj_val=False, indent=indent, force_flow=True):
yield chunk
self._pop_obj(o)
yield indent[:-len(self._indent)]
yield ']\n'
elif isinstance(o, dict):
items = [(self._escape_key(k), v) for k, v in o.items()]
if self._skipkeys:
items = [(k, v) for k, v in items if k is not None]
if self._sort_keys:
items.sort()
if force_flow or not items:
if not items:
if obj_val:
yield ' {}\n'
else:
yield indent
yield '{}\n'
else:
if obj_val:
yield ' {\n'
else:
yield indent
yield '{\n'
indent = indent + self._indent
self._push_obj(o)
for k, v in items:
yield indent
yield k
yield ':'
for chunk in self._encode(v, obj_val=True, indent=indent + self._indent, force_flow=False):
yield chunk
self._pop_obj(o)
yield indent[:-len(self._indent)]
yield '}\n'
else:
if obj_val:
yield '\n'
self._push_obj(o)
for k, v in items:
yield indent
yield k
yield ':'
for chunk in self._encode(v, obj_val=True, indent=indent + self._indent, force_flow=False):
yield chunk
self._pop_obj(o)
else:
v = self._format_simple_val(o)
if v is None:
self._push_obj(o)
v = self.default(o)
for chunk in self._encode(v, obj_val=obj_val, indent=indent, force_flow=force_flow):
yield chunk
self._pop_obj(o)
else:
if obj_val:
yield ' '
else:
yield indent
yield v
yield '\n'
def iterencode(self, o):
return self._encode(o)
def encode(self, o):
return ''.join(self.iterencode(o))
def default(self, o):
if self._default is None:
raise TypeError('Cannot serialize an object of type {}'.format(type(o).__name__))
return self._default(o)
def dump(obj, fp, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None,
indent=None, default=None, sort_keys=False, **kw):
if indent is None and cls is None:
return json.dump(obj, fp, skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular,
allow_nan=allow_nan, default=default, sort_keys=sort_keys, separators=(',', ':'))
if cls is None:
cls = CSONEncoder
encoder = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular,
allow_nan=allow_nan, sort_keys=sort_keys, indent=indent, default=default, **kw)
for chunk in encoder.iterencode(obj):
fp.write(chunk)
def dumps(obj, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, cls=None, indent=None,
default=None, sort_keys=False, **kw):
if indent is None and cls is None:
return json.dumps(obj, skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular,
allow_nan=allow_nan, default=default, sort_keys=sort_keys, separators=(',', ':'))
if cls is None:
cls = CSONEncoder
encoder = cls(skipkeys=skipkeys, ensure_ascii=ensure_ascii, check_circular=check_circular,
allow_nan=allow_nan, sort_keys=sort_keys, indent=indent, default=default, **kw)
return encoder.encode(obj)

View File

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

View File

@@ -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 = "Snippets Completer",
provider = self.provider,
language_ids = []
)
self.message_to("completion", event)
def run(self):
...

View File

@@ -0,0 +1,68 @@
# 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 Snippits Completion Provider.
# NOTE: used information from here --> https://warroom.rsmus.com/do-that-auto-complete/
"""
__gtype_name__ = 'SnippetsCompletionProvider'
def __init__(self):
super(Provider, self).__init__()
self.response_cache: ProviderResponseCache = ProviderResponseCache()
def do_get_name(self):
return 'Snippits 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 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
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(word)
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)

View File

@@ -0,0 +1,68 @@
# 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
from . import cson
class ProviderResponseCache(ProviderResponseCacheBase):
def __init__(self):
super(ProviderResponseCache, self).__init__()
self.matchers: dict = {}
self.load_snippets()
def load_snippets(self):
fpath = path.join(path.dirname(path.realpath(__file__)), "snippets.cson")
with open(fpath, 'rb') as f:
self.snippets = cson.load(f)
for group in self.snippets:
self.snippets[group]
for entry in self.snippets[group]:
data = self.snippets[group][entry]
self.matchers[ data["prefix"] ] = {
"label": entry,
"text": data["body"],
"info": GLib.markup_escape_text( data["body"] )
}
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]:
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]:
response: list[dict] = []
return response

View File

@@ -0,0 +1,614 @@
# Your snippets
#
# Atom snippets allow you to enter a simple prefix in the editor and hit tab to
# expand the prefix into a larger code block with templated values.
#
# You can create a new snippet in this file by typing "snip" and then hitting
# tab.
#
# An example CoffeeScript snippet to expand log to console.log:
#
# '.source.coffee':
# 'Console log':
# 'prefix': 'log'
# 'body': 'console.log $1'
#
# Each scope (e.g. '.source.coffee' above) can only be declared once.
#
# This file uses CoffeeScript Object Notation (CSON).
# If you are unfamiliar with CSON, you can read more about it in the
# Atom Flight Manual:
# http://flight-manual.atom.io/using-atom/sections/basic-customization/#_cson
### HTML SNIPPETS ###
'.text.html.basic':
'HTML Template':
'prefix': 'html'
'body': """<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<link rel="shortcut icon" href="fave_icon.png">
<link rel="stylesheet" href="resources/css/base.css">
<link rel="stylesheet" href="resources/css/main.css">
</head>
<body>
<script src="resources/js/.js" charset="utf-8"></script>
<script src="resources/js/.js" charset="utf-8"></script>
</body>
</html>
"""
'Canvas Tag':
'prefix': 'canvas'
'body': """<canvas id="canvas" width="800" height="600" style="border:1px solid #c3c3c3;"></canvas>"""
'Img Tag':
'prefix': 'img'
'body': """<img class="" src="" alt="" />"""
'Br Tag':
'prefix': 'br'
'body': """<br/>"""
'Hr Tag':
'prefix': 'hr'
'body': """<hr/>"""
'Server Side Events':
'prefix': 'sse'
'body': """// SSE events if supported
if(typeof(EventSource) !== "undefined") {
let source = new EventSource("resources/php/sse.php");
source.onmessage = (event) => {
if (event.data === "<yourDataStringToLookFor>") {
// code here
}
};
} else {
console.log("SSE Not Supported In Browser...");
}
"""
'AJAX Template Function':
'prefix': 'ajax template'
'body': """const doAjax = async (actionPath, data) => {
let xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
if (this.responseText != null) { // this.responseXML if getting XML fata
handleReturnData(JSON.parse(this.responseText));
} else {
console.log("No content returned. Check the file path.");
}
}
};
xhttp.open("POST", actionPath, true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// Force return to be JSON NOTE: Use application/xml to force XML
xhttp.overrideMimeType('application/json');
xhttp.send(data);
}
"""
'CSS Message Colors':
'prefix': 'css colors'
'body': """.error { color: rgb(255, 0, 0); }
.warning { color: rgb(255, 168, 0); }
.success { color: rgb(136, 204, 39); }
"""
### JS SNIPPETS ###
'.source.js':
'Server Side Events':
'prefix': 'sse'
'body': """// SSE events if supported
if(typeof(EventSource) !== "undefined") {
let source = new EventSource("resources/php/sse.php");
source.onmessage = (event) => {
if (event.data === "<yourDataStringToLookFor>") {
// code here
}
};
} else {
console.log("SSE Not Supported In Browser...");
}
"""
'AJAX Template Function':
'prefix': 'ajax template'
'body': """const doAjax = async (actionPath, data) => {
let xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState === 4 && this.status === 200) {
if (this.responseText != null) { // this.responseXML if getting XML fata
handleReturnData(JSON.parse(this.responseText));
} else {
console.log("No content returned. Check the file path.");
}
}
};
xhttp.open("POST", actionPath, true);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
// Force return to be JSON NOTE: Use application/xml to force XML
xhttp.overrideMimeType('application/json');
xhttp.send(data);
}
"""
'SE6 Function':
'prefix': 'function se6'
'body': """const funcName = (arg = "") => {
}
"""
### CSS SNIPPETS ###
'.source.css':
'CSS Message Colors':
'prefix': 'css colors'
'body': """.error { color: rgb(255, 0, 0); }
.warning { color: rgb(255, 168, 0); }
.success { color: rgb(136, 204, 39); }
"""
### PHP SNIPPETS ###
'.text.html.php':
'SSE PHP':
'prefix': 'sse php'
'body': """<?php
// Start the session
session_start();
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
echo "data:dataToReturn\\\\n\\\\n";
flush();
?>
"""
'PHP Template':
'prefix': 'php'
'body': """<?php
// Start the session
session_start();
// Determin action
chdir("../../"); // Note: If in resources/php/
if (isset($_POST['yourPostID'])) {
// code here
} else {
$message = "Server: [Error] --> Illegal Access Method!";
serverMessage("error", $message);
}
?>
"""
'HTML Template':
'prefix': 'html'
'body': """<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title></title>
<link rel="shortcut icon" href="fave_icon.png">
<link rel="stylesheet" href="resources/css/base.css">
<link rel="stylesheet" href="resources/css/main.css">
</head>
<body>
<script src="resources/js/.js" charset="utf-8"></script>
<script src="resources/js/.js" charset="utf-8"></script>
</body>
</html>
"""
### BASH SNIPPETS ###
'.source.shell':
'Bash or Shell Template':
'prefix': 'bash template'
'body': """#!/bin/bash
# . CONFIG.sh
# set -o xtrace ## To debug scripts
# set -o errexit ## To exit on error
# set -o errunset ## To exit if a variable is referenced but not set
function main() {
cd "$(dirname "$0")"
echo "Working Dir: " $(pwd)
file="$1"
if [ -z "${file}" ]; then
echo "ERROR: No file argument supplied..."
exit
fi
if [[ -f "${file}" ]]; then
echo "SUCCESS: The path and file exists!"
else
echo "ERROR: The path or file '${file}' does NOT exist..."
fi
}
main "$@";
"""
'Bash or Shell Config':
'prefix': 'bash config'
'body': """#!/bin/bash
shopt -s expand_aliases
alias echo="echo -e"
"""
### PYTHON SNIPPETS ###
'.source.python':
'Glade __main__ Class Template':
'prefix': 'glade __main__ class'
'body': """#!/usr/bin/python3
# Python imports
import argparse
import faulthandler
import traceback
import signal
from setproctitle import setproctitle
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GLib
# Application imports
from app import Application
def run():
try:
setproctitle('<replace this>')
faulthandler.enable() # For better debug info
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit)
parser = argparse.ArgumentParser()
# Add long and short arguments
parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.")
parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.")
parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.")
parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.")
parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.")
# Read arguments (If any...)
args, unknownargs = parser.parse_known_args()
main = Application(args, unknownargs)
Gtk.main()
except Exception as e:
traceback.print_exc()
quit()
if __name__ == "__main__":
''' Set process title, get arguments, and create GTK main thread. '''
run()
"""
'Glade __main__ Testing Template':
'prefix': 'glade testing class'
'body': """#!/usr/bin/python3
# Python imports
import traceback
import faulthandler
import signal
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GLib
# Application imports
app_name = "Gtk Quick Test"
class Application(Gtk.ApplicationWindow):
def __init__(self):
super(Application, self).__init__()
self._setup_styling()
self._setup_signals()
self._load_widgets()
self.add(Gtk.Box())
self.show_all()
def _setup_styling(self):
self.set_default_size(1670, 830)
self.set_title(f"{app_name}")
# self.set_icon_from_file( settings.get_window_icon() )
self.set_gravity(5) # 5 = CENTER
self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS
def _setup_signals(self):
self.connect("delete-event", Gtk.main_quit)
def _load_widgets(self):
...
def run():
try:
faulthandler.enable() # For better debug info
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit)
main = Application()
Gtk.main()
except Exception as e:
traceback.print_exc()
quit()
if __name__ == "__main__":
''' Set process title, get arguments, and create GTK main thread. '''
run()
"""
'Glade _init_ Class Template':
'prefix': 'glade __init__ class'
'body': """# Python imports
import inspect
# Lib imports
# Application imports
from utils import Settings
from signal_classes import CrossClassSignals
class Main:
def __init__(self, args):
settings = Settings()
builder = settings.returnBuilder()
# Gets the methods from the classes and sets to handler.
# Then, builder connects to any signals it needs.
classes = [CrossClassSignals(settings)]
handlers = {}
for c in classes:
methods = inspect.getmembers(c, predicate=inspect.ismethod)
handlers.update(methods)
builder.connect_signals(handlers)
window = settings.createWindow()
window.show()
"""
'Class Method':
'prefix': 'def1'
'body': """
def fname(self):
...
"""
'Gtk Class Method':
'prefix': 'def2'
'body': """
def fname(self, widget, eve):
...
"""
'Python Glade Settings Template':
'prefix': 'glade settings class'
'body': """# Python imports
import os
# Lib imports
import gi, cairo
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
# Application imports
class Settings:
def __init__(self):
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) + "/"
self.builder = Gtk.Builder()
self.builder.add_from_file(self.SCRIPT_PTH + "../resources/Main_Window.glade")
# 'Filters'
self.office = ('.doc', '.docx', '.xls', '.xlsx', '.xlt', '.xltx', '.xlm',
'.ppt', 'pptx', '.pps', '.ppsx', '.odt', '.rtf')
self.vids = ('.mkv', '.avi', '.flv', '.mov', '.m4v', '.mpg', '.wmv',
'.mpeg', '.mp4', '.webm')
self.txt = ('.txt', '.text', '.sh', '.cfg', '.conf')
self.music = ('.psf', '.mp3', '.ogg' , '.flac')
self.images = ('.png', '.jpg', '.jpeg', '.gif')
self.pdf = ('.pdf')
def createWindow(self):
# Get window and connect signals
window = self.builder.get_object("Main_Window")
window.connect("delete-event", gtk.main_quit)
self.setWindowData(window, False)
return window
def setWindowData(self, window, paintable):
screen = window.get_screen()
visual = screen.get_rgba_visual()
if visual != None and screen.is_composited():
window.set_visual(visual)
# bind css file
cssProvider = gtk.CssProvider()
cssProvider.load_from_path(self.SCRIPT_PTH + '../resources/stylesheet.css')
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
window.set_app_paintable(paintable)
if paintable:
window.connect("draw", self.area_draw)
def getMonitorData(self):
screen = self.builder.get_object("Main_Window").get_screen()
monitors = []
for m in range(screen.get_n_monitors()):
monitors.append(screen.get_monitor_geometry(m))
for monitor in monitors:
print(str(monitor.width) + "x" + str(monitor.height) + "+" + str(monitor.x) + "+" + str(monitor.y))
return monitors
def area_draw(self, widget, cr):
cr.set_source_rgba(0, 0, 0, 0.54)
cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint()
cr.set_operator(cairo.OPERATOR_OVER)
def returnBuilder(self): return self.builder
# Filter returns
def returnOfficeFilter(self): return self.office
def returnVidsFilter(self): return self.vids
def returnTextFilter(self): return self.txt
def returnMusicFilter(self): return self.music
def returnImagesFilter(self): return self.images
def returnPdfFilter(self): return self.pdf
"""
'Python Glade CrossClassSignals Template':
'prefix': 'glade crossClassSignals class'
'body': """# Python imports
import threading
import subprocess
import os
# Lib imports
# Application imports
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs).start()
return wrapper
class CrossClassSignals:
def __init__(self, settings):
self.settings = settings
self.builder = self.settings.returnBuilder()
def getClipboardData(self):
proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE)
retcode = proc.wait()
data = proc.stdout.read()
return data.decode("utf-8").strip()
def setClipboardData(self, data):
proc = subprocess.Popen(['xclip','-selection','clipboard'], stdin=subprocess.PIPE)
proc.stdin.write(data)
proc.stdin.close()
retcode = proc.wait()
"""
'Python Glade Generic Template':
'prefix': 'glade generic class'
'body': """# Python imports
# Lib imports
# Application imports
class GenericClass:
def __init__(self):
super(GenericClass, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
...
def _setup_signals(self):
...
def _subscribe_to_events(self):
event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
def _load_widgets(self):
...
"""

View File

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

View File

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

View File

@@ -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

View File

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

View File

@@ -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):
...

View File

@@ -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)

View File

@@ -0,0 +1,117 @@
# Python imports
import asyncio
# 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):
buffer = event.file.buffer
asyncio.run( self._handle_change(buffer) )
def process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
self.matchers[event.file.buffer] = set()
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
asyncio.run( self._handle_change(buffer) )
async def _handle_change(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:
GLib.idle_add(self.load_empty_set, buffer)
return
if not buffer in self.matchers:
GLib.idle_add(self.load_as_new_set, buffer, data)
return
new_words = self.get_all_words(data)
GLib.idle_add(self.load_into_set, buffer, new_words)
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_empty_set(self, buffer):
self.matchers[buffer] = set()
def load_into_set(self, buffer, new_words):
self.matchers[buffer].update(new_words)
def load_as_new_set(self, buffer, data):
self.matchers[buffer] = self.get_all_words(data)
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

View File

@@ -1,13 +1,9 @@
{ {
"manifest": { "name": "Example Plugin",
"name": "Example Plugin", "author": "John Doe",
"author": "John Doe", "version": "0.0.1",
"version": "0.0.1", "support": "",
"support": "", "requests": {
"requests": { "bind_keys": ["Example Plugin||send_message:<Control>f"]
"ui_target": "plugin_control_list",
"pass_events": true,
"bind_keys": ["Example Plugin||send_message:<Control>f"]
}
} }
} }

View File

@@ -1,8 +1,4 @@
# Python imports # Python imports
import os
import threading
import subprocess
import time
# Lib imports # Lib imports
import gi import gi
@@ -10,42 +6,36 @@ 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 plugins.plugin_base import PluginBase from plugins.plugin_base import PluginBase
# NOTE: Threads WILL NOT die with parent's destruction.
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
return wrapper
# NOTE: Threads WILL die with parent's destruction.
def daemon_threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class Plugin(PluginBase): class Plugin(PluginBase):
def __init__(self): def __init__(self):
super().__init__() super(Plugin, self).__init__()
self.name = "Example Plugin" # 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 _controller_message(self, event: BaseEvent):
button = Gtk.Button(label=self.name) ...
button.connect("button-release-event", self.send_message)
return button def load(self):
ui_element = self.requests_ui_element("plugin_control_list")
ui_element.add( self.generate_plugin_element() )
def run(self): def run(self):
... ...
def generate_plugin_element(self):
button = Gtk.Button(label = self.name)
def send_message(self, widget=None, eve=None): button.connect("button-release-event", self.send_message)
button.show()
return button
def send_message(self, widget = None, eve = None):
message = "Hello, World!" message = "Hello, World!"
event_system.emit("display_message", ("warning", message, None)) self.emit("display_message", ("warning", message, None))

View File

@@ -9,10 +9,10 @@ import sys
# Application imports # Application imports
# from libs.db import DB # from libs.db import DB
from libs.event_system import EventSystem from libs.event_system import EventSystem
from libs.endpoint_registry import EndpointRegistry
from libs.keybindings import Keybindings from libs.keybindings import Keybindings
from libs.logger import Logger from libs.logger import Logger
from libs.settings.manager import SettingsManager from libs.settings.manager import SettingsManager
from libs.widget_registery import WidgetRegisteryController
@@ -34,12 +34,8 @@ def daemon_threaded_wrapper(fn):
def call_chain_wrapper(fn): def call_chain_wrapper(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
print()
print()
for line in traceback.format_stack(): for line in traceback.format_stack():
print( line.strip() ) print( line.strip() )
print()
print()
return fn(*args, **kwargs) return fn(*args, **kwargs)
return wrapper return wrapper
@@ -51,8 +47,8 @@ builtins.APP_NAME = "<change_me>"
builtins.keybindings = Keybindings() builtins.keybindings = Keybindings()
builtins.event_system = EventSystem() builtins.event_system = EventSystem()
builtins.endpoint_registry = EndpointRegistry()
builtins.settings_manager = SettingsManager() builtins.settings_manager = SettingsManager()
builtins.widget_registery = WidgetRegisteryController()
# builtins.db = DB() # builtins.db = DB()
settings_manager.load_settings() settings_manager.load_settings()

View File

@@ -1,33 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class BuilderWrapper(Gtk.Builder):
"""docstring for BuilderWrapper."""
def __init__(self):
super(BuilderWrapper, self).__init__()
self.objects = {}
def get_object(self, id: str, use_gtk: bool = True) -> any:
if not use_gtk:
return self.objects[id]
return super(BuilderWrapper, self).get_object(id)
def expose_object(self, id: str, object: any, use_gtk: bool = True) -> None:
if not use_gtk:
self.objects[id] = object
else:
super(BuilderWrapper, self).expose_object(id, object)
def dereference_object(self, id: str) -> None:
del self.objects[id]

View File

@@ -14,7 +14,6 @@ class CenterContainer(Gtk.Box):
def __init__(self): def __init__(self):
super(CenterContainer, self).__init__() super(CenterContainer, self).__init__()
self._builder = settings_manager.get_builder()
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
@@ -39,7 +38,7 @@ class CenterContainer(Gtk.Box):
... ...
def _load_widgets(self): def _load_widgets(self):
glade_box = self._builder.get_object("glade_box") glade_box = widget_registery.get_object("glade_box")
button = Gtk.Button(label = "Click Me!") button = Gtk.Button(label = "Click Me!")
button.connect("clicked", self._hello_world) button.connect("clicked", self._hello_world)

View File

@@ -6,33 +6,48 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from plugins import plugins_controller
from libs.mixins.ipc_signals_mixin import IPCSignalsMixin 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 .base_controller_data import BaseControllerData from .base_controller_mixin import BaseControllerMixin
from .bridge_controller import BridgeController from .bridge_controller import BridgeController
class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData): class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin):
""" docstring for BaseController. """ """ docstring for BaseController. """
def __init__(self): def __init__(self):
self._setup_controller_data() self._setup_controller_data()
self._load_plugins(is_pre = True)
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
self._subscribe_to_events() self._subscribe_to_events()
self._load_controllers() self._load_controllers()
self._load_plugins_and_files() self._load_plugins(is_pre = False)
self._load_files()
logger.info(f"Made it past {self.__class__} loading...") logger.info(f"Made it past {self.__class__} loading...")
settings_manager.set_end_load_time() settings_manager.set_end_load_time()
settings_manager.log_load_time() settings_manager.log_load_time()
def _setup_controller_data(self):
self.window = settings_manager.get_main_window()
self.base_container = BaseContainer()
self.plugins_controller = plugins_controller
widget_registery.expose_object("main_window", self.window)
settings_manager.register_signals_to_builder([self, self.base_container])
self._collect_files_dirs()
def _setup_styling(self): def _setup_styling(self):
... ...
@@ -50,23 +65,22 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
def _load_controllers(self): def _load_controllers(self):
BridgeController() BridgeController()
def _load_plugins_and_files(self): def _load_plugins(self, is_pre: bool):
args, unknownargs = settings_manager.get_starting_args() args, unknownargs = settings_manager.get_starting_args()
if args.no_plugins == "false": if args.no_plugins == "true": return
self.plugins_controller.pre_launch_plugins()
self.plugins_controller.post_launch_plugins()
if is_pre:
self.plugins_controller.pre_launch_plugins()
return
if not is_pre:
self.plugins_controller.post_launch_plugins()
return
def _load_files(self):
for file in settings_manager.get_starting_files(): for file in settings_manager.get_starting_files():
event_system.emit("post-file-to-ipc", file) event_system.emit("post-file-to-ipc", file)
def _tggl_top_main_menubar(self): def _tggl_top_main_menubar(self):
logger.debug("_tggl_top_main_menubar > stub...") logger.debug("_tggl_top_main_menubar > stub...")
def _load_glade_file(self):
self.builder.add_from_file( settings_manager.path_manager.get_glade_file() )
self.builder.expose_object("main_window", self.window)
settings_manager.set_builder(self.builder)
self.base_container = BaseContainer()
settings_manager.register_signals_to_builder([self, self.base_container])

View File

@@ -6,28 +6,11 @@ from shutil import which
# Lib imports # Lib imports
# Application imports # Application imports
from plugins.plugins_controller import PluginsController
from ..builder_wrapper import BuilderWrapper
class BaseControllerData: class BaseControllerMixin:
''' BaseControllerData contains most of the state of the app at ay given time. It also has some support methods. ''' ''' BaseControllerMixin contains most of the state of the app at ay given time. It also has some support methods. '''
def _setup_controller_data(self) -> None:
self.window = settings_manager.get_main_window()
self.builder = BuilderWrapper()
self.plugins_controller = PluginsController()
self.base_container = None
self.was_midified_key = False
self.ctrl_down = False
self.shift_down = False
self.alt_down = False
self._collect_files_dirs()
self._load_glade_file()
def _collect_files_dirs(self): def _collect_files_dirs(self):
args, \ args, \

View File

@@ -3,12 +3,15 @@
# Lib imports # Lib imports
# Application imports # Application imports
from .controllers.controller_manager import ControllerManager from plugins import plugins_controller
from libs.controllers.controller_manager import ControllerManager
from .controllers.files_controller import FilesController from .controllers.files_controller import FilesController
from .controllers.tabs_controller import TabsController from .controllers.tabs_controller import TabsController
from .controllers.commands_controller import CommandsController from .controllers.commands_controller import CommandsController
from .controllers.completion_controller import CompletionController from .controllers.completion_controller import CompletionController
from .controllers.source_views_controller import SourceViewsController from .controllers.views.source_views_controller import SourceViewsController
from .mini_view_widget import MiniViewWidget from .mini_view_widget import MiniViewWidget
@@ -31,11 +34,14 @@ class CodeBase:
completion_controller = CompletionController() completion_controller = CompletionController()
source_views_controller = SourceViewsController() source_views_controller = SourceViewsController()
# self.controller_manager.register_controller("base", self)
self.controller_manager.register_controller("files", files_controller) self.controller_manager.register_controller("files", files_controller)
self.controller_manager.register_controller("tabs", tabs_controller) self.controller_manager.register_controller("tabs", tabs_controller)
self.controller_manager.register_controller("commands", commands_controller) self.controller_manager.register_controller("commands", commands_controller)
self.controller_manager.register_controller("completion", completion_controller) self.controller_manager.register_controller("completion", completion_controller)
self.controller_manager.register_controller("source_views", source_views_controller) self.controller_manager.register_controller("source_views", source_views_controller)
self.controller_manager.register_controller("plugins", plugins_controller)
self.controller_manager.register_controller("widgets", widget_registery)
def get_tabs_widget(self): def get_tabs_widget(self):
return self.controller_manager["tabs"].get_tabs_widget() return self.controller_manager["tabs"].get_tabs_widget()
@@ -44,7 +50,12 @@ class CodeBase:
return self.miniview_widget return self.miniview_widget
def create_source_view(self): def create_source_view(self):
return self.controller_manager["source_views"].create_source_view() source_view = self.controller_manager["source_views"].create_source_view()
self.controller_manager["completion"].register_completer(
source_view.get_completion()
)
return source_view
def first_map_load(self): def first_map_load(self):
self.controller_manager["source_views"].first_map_load() self.controller_manager["source_views"].first_map_load()

View File

@@ -0,0 +1,5 @@
"""
Code Command System Package
"""
from .command_system import CommandSystem

View File

@@ -9,7 +9,7 @@ from gi.repository import GtkSource
def set_language_and_style(view, file): def set_language_and_style(view, file):
language = view.language_manager.guess_language(file.fname, None) language = view.language_manager.guess_language(file.fname, None)
file.ftype = language file.ftype = "buffer" if not language else language
file.buffer.set_language(language) file.buffer.set_language(language)
file.buffer.set_style_scheme(view.syntax_theme) file.buffer.set_style_scheme(view.syntax_theme)

View File

@@ -3,13 +3,12 @@
# Lib imports # Lib imports
# Application imports # Application imports
from libs.dto.code import CodeEvent from libs.event_factory import Event_Factory, Code_Event_Types
from .event_factory import Event_Factory, Event_Factory_Types
from ..source_view import SourceView
from . import commands from . import commands
from .source_view import SourceView
class CommandSystem: class CommandSystem:
@@ -39,17 +38,18 @@ class CommandSystem:
return method.execute(*args) return method.execute(*args)
def emit(self, event: CodeEvent): def emit(self, event: Code_Event_Types.CodeEvent):
""" Monky patch 'emit' from command controller... """ """ Monkey patch 'emit' from command controller... """
... ...
def emit_to(self, controller: str, event: CodeEvent): def emit_to(self, controller: str, event: Code_Event_Types.CodeEvent):
""" Monky patch 'emit' from command controller... """ """ Monkey patch 'emit_to' from command controller... """
... ...
def get_file(self, view: SourceView): def get_file(self, view: SourceView):
event = Event_Factory.create_get_file( event = Event_Factory.create_event(
"get_file",
view = view, view = view,
buffer = view.get_buffer() buffer = view.get_buffer()
) )
@@ -59,7 +59,8 @@ class CommandSystem:
return event.response return event.response
def get_swap_file(self, view: SourceView): def get_swap_file(self, view: SourceView):
event = Event_Factory.create_get_swap_file( event = Event_Factory.create_event(
"get_swap_file",
view = view, view = view,
buffer = view.get_buffer() buffer = view.get_buffer()
) )
@@ -76,7 +77,8 @@ class CommandSystem:
return event.response return event.response
def remove_file(self, view: SourceView): def remove_file(self, view: SourceView):
event = Event_Factory.create_remove_file( event = Event_Factory.create_event(
"removed_file",
view = view, view = view,
buffer = view.get_buffer() buffer = view.get_buffer()
) )

View File

@@ -1,5 +1,5 @@
""" """
Commands Package Code Commands Package
""" """
import pkgutil import pkgutil

View File

@@ -9,7 +9,6 @@ from gi.repository import GtkSource
from gi.repository import Gio from gi.repository import Gio
# Application imports # Application imports
from ..source_file import SourceFile

View File

@@ -9,7 +9,7 @@ from gi.repository import GtkSource
from gi.repository import Gio from gi.repository import Gio
# Application imports # Application imports
from ..source_file import SourceFile from ...source_file import SourceFile
from ..command_helpers import set_language_and_style from ..command_helpers import set_language_and_style

View File

@@ -18,7 +18,7 @@ def execute(
): ):
logger.debug("Command: New File") logger.debug("Command: New File")
file = view.command.new_file(view) file = view.command.new_file(view)
set_language_and_style(view, file) set_language_and_style(view, file)
view.set_buffer(file.buffer) view.set_buffer(file.buffer)

View File

@@ -9,7 +9,7 @@ gi.require_version('GtkSource', '4')
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
from ..source_file import SourceFile from ...source_file import SourceFile
from ..command_helpers import update_info_bar_if_focused from ..command_helpers import update_info_bar_if_focused

View File

@@ -6,12 +6,8 @@ import gi
gi.require_version('GtkSource', '4') gi.require_version('GtkSource', '4')
from gi.repository import GtkSource from gi.repository import GtkSource
from gi.repository import Gio
# Application imports # Application imports
from libs.dto.code import FocusedViewEvent
from ..source_file import SourceFile

View File

@@ -0,0 +1,28 @@
# Python imports
# Lib imports
import gi
gi.require_version('GtkSource', '4')
from gi.repository import GtkSource
# Application imports
def execute(
view: GtkSource.View = None
):
logger.debug("Command: Show Completion")
completer = view.get_completion()
providers = completer.get_providers()
if not providers:
view.command.request_completion(view)
return
completer.start(
providers,
completer.create_context()
)

View File

@@ -6,10 +6,8 @@ import gi
gi.require_version('GtkSource', '4') gi.require_version('GtkSource', '4')
from gi.repository import GtkSource from gi.repository import GtkSource
from gi.repository import Gio
# Application imports # Application imports
from ..source_file import SourceFile

View File

@@ -1,18 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('GtkSource', '4')
from gi.repository import GtkSource
# Application imports
def execute(
view: GtkSource.View = None
):
logger.debug("Command: Show Completion")
view.command.request_completion(view)

View File

@@ -1,3 +1,3 @@
""" """
Custom Completion Providers Module Code Completion Providers Package
""" """

View File

@@ -1,84 +0,0 @@
# 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 GtkSource
from gi.repository import GObject
# Application imports
class ExampleCompletionProvider(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):
GObject.Object.__init__(self)
def do_get_name(self):
""" Returns: a new string containing the name of the provider. """
return 'Example Completion Provider'
def do_match(self, context):
""" Get whether the provider match the context of completion detailed in context. """
# NOTE: True for debugging but context needs to normally get checked for actual usage needs.
# TODO: Fix me
return True
def do_get_priority(self):
""" Determin position in result list along other providor results. """
return 1
# 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):
"""
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 = [
GtkSource.CompletionItem(label='Hello World!', text = 'Hello World!', icon = None, info = None) # NOTE: Always proposed...
]
# 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 end_iter:
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):
proposals.append(
GtkSource.CompletionItem(label='foo', text='foo }}') # optionally proposed based on left search via regex
)
proposals.append(
GtkSource.CompletionItem(label='bar', text='bar }}') # optionally proposed based on left search via regex
)
context.add_proposals(self, proposals, True)

View File

@@ -1,137 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import Gtk
from gi.repository import GtkSource
from gi.repository import GObject
# Application imports
class LSPCompletionProvider(GObject.Object, GtkSource.CompletionProvider):
"""
This code is an LSP code completion plugin for Newton.
# NOTE: Some code pulled/referenced from here --> https://github.com/isamert/gedi
"""
__gtype_name__ = 'LSPProvider'
def __init__(self):
GObject.Object.__init__(self)
self._icon_theme = Gtk.IconTheme.get_default()
self.lsp_data = None
def pre_populate(self, context):
...
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):
iter = self.get_iter_correctly(context)
iter.backward_char()
buffer = iter.get_buffer()
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
return False
ch = iter.get_char()
# NOTE: Look to re-add or apply supprting logic to use spaces
# As is it slows down the editor in certain contexts...
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
if not (ch in ('_', '.') or ch.isalnum()):
return False
return True
def do_get_priority(self):
return 5
def do_populate(self, context, items = []):
# self.lsp_data
proposals = []
comp_item = GtkSource.CompletionItem.new()
comp_item.set_label("LSP Class")
comp_item.set_text("LSP Code")
# comp_item.set_icon(self.get_icon_for_type(completion.type))
comp_item.set_info("A test LSP completion item...")
context.add_proposals(self, [comp_item], True)
# def do_populate(self, context, items = []):
# if hasattr(self._source_view, "completion_items"):
# items = self._source_view.completion_items
# proposals = []
# for item in items:
# proposals.append( self.create_completion_item(item) )
# context.add_proposals(self, proposals, True)
# def get_icon_for_type(self, _type):
# try:
# return self._theme.load_icon(icon_names[_type.lower()], 16, 0)
# except:
# ...
# try:
# return self._theme.load_icon(Gtk.STOCK_ADD, 16, 0)
# except:
# ...
# return None
# def create_completion_item(self, item):
# comp_item = GtkSource.CompletionItem.new()
# keys = item.keys()
# comp_item.set_label(item["label"])
# if "insertText" in keys:
# comp_item.set_text(item["insertText"])
# if "additionalTextEdits" in keys:
# comp_item.additionalTextEdits = item["additionalTextEdits"]
# return comp_item
# def create_completion_item(self, item):
# comp_item = GtkSource.CompletionItem.new()
# comp_item.set_label(item.label)
# if item.textEdit:
# if isinstance(item.textEdit, dict):
# comp_item.set_text(item.textEdit["newText"])
# else:
# comp_item.set_text(item.textEdit)
# else:
# comp_item.set_text(item.insertText)
# comp_item.set_icon( self.get_icon_for_type(item.kind) )
# comp_item.set_info(item.documentation)
# return comp_item

View File

@@ -0,0 +1,119 @@
# 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
class ProviderResponseCacheException(Exception):
...
class ProviderResponseCacheBase:
def __init__(self):
super(ProviderResponseCacheBase, self).__init__()
self._icon_theme = Gtk.IconTheme.get_default()
def process_file_load(self, buffer: GtkSource.Buffer):
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_load' not implemented...")
def process_file_close(self, buffer: GtkSource.Buffer):
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_close' not implemented...")
def process_file_save(self, buffer: GtkSource.Buffer):
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_file_save' not implemented...")
def process_file_change(self, buffer: GtkSource.Buffer):
raise ProviderResponseCacheException("ProviderResponseCacheBase 'process_change' not implemented...")
def filter(self, word: str) -> list[dict]:
raise ProviderResponseCacheException("ProviderResponseCacheBase 'filter' not implemented...")
def filter_with_context(self, context: GtkSource.CompletionContext) -> list[dict]:
raise ProviderResponseCacheException("ProviderResponseCacheBase 'filter_with_context' not implemented...")
def create_completion_item(
self,
label: str = "",
text: str = "",
info: str = "",
icon: any = None
) -> dict:
if not label or not text: return
comp_item = GtkSource.CompletionItem.new()
comp_item.set_label(label)
comp_item.set_text(text)
if info:
comp_item.set_info(info)
# comp_item.set_markup(f"<h3>{info}</h3>")
if icon:
comp_item.set_icon(
self.get_icon_for_type(icon.type)
)
return comp_item
def get_all_marks(self, buffer) -> list:
marks: list = []
iter_ = buffer.get_start_iter()
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) -> list:
marks: list = []
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()
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) -> Gtk.TextIter:
return context.get_iter()[1] if isinstance(context.get_iter(), tuple) else context.get_iter()

View File

@@ -1,107 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4')
from gi.repository import Gtk
from gi.repository import GtkSource
from gi.repository import GObject
import jedi
from jedi.api import Script
# Application imports
# FIXME: Find real icon names...
icon_names = {
'import': '',
'module': '',
'class': '',
'function': '',
'statement': '',
'param': ''
}
class Jedi:
def get_script(file, doc_text):
return Script(code = doc_text, path = file)
class PythonCompletionProvider(GObject.Object, GtkSource.CompletionProvider):
"""
This code is A python code completion plugin for Newton.
# NOTE: Some code pulled/referenced from here --> https://github.com/isamert/gedi
"""
__gtype_name__ = 'PythonProvider'
def __init__(self, file):
GObject.Object.__init__(self)
self._theme = Gtk.IconTheme.get_default()
self._file = file
def do_get_name(self):
return "Python 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):
iter = self.get_iter_correctly(context)
iter.backward_char()
buffer = iter.get_buffer()
if buffer.get_context_classes_at_iter(iter) != ['no-spell-check']:
return False
ch = iter.get_char()
# NOTE: Look to re-add or apply supprting logic to use spaces
# As is it slows down the editor in certain contexts...
# if not (ch in ('_', '.', ' ') or ch.isalnum()):
if not (ch in ('_', '.') or ch.isalnum()):
return False
return True
def do_get_priority(self):
return 1
def do_get_activation(self):
return GtkSource.CompletionActivation.INTERACTIVE
def do_populate(self, context):
# TODO: Maybe convert async?
it = self.get_iter_correctly(context)
buffer = it.get_buffer()
proposals = []
doc_text = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), False)
iter_cursor = buffer.get_iter_at_mark(buffer.get_insert())
linenum = iter_cursor.get_line() + 1
charnum = iter_cursor.get_line_index()
def create_generator():
for completion in Jedi.get_script(self._file, doc_text).complete(line = linenum, column = None, fuzzy = False):
comp_item = GtkSource.CompletionItem.new()
comp_item.set_label(completion.name)
comp_item.set_text(completion.name)
comp_item.set_icon(self.get_icon_for_type(completion.type))
comp_item.set_info(completion.docstring())
yield comp_item
for item in create_generator():
proposals.append(item)
context.add_proposals(self, proposals, True)
def get_icon_for_type(self, _type):
try:
return self._theme.load_icon(icon_names[_type.lower()], 16, 0)
except (KeyError, AttributeError, GObject.GError) as e:
return self._theme.load_icon(Gtk.STOCK_ADD, 16, 0)
except (GObject.GError, AttributeError) as e:
return None

View File

@@ -1,3 +1,3 @@
""" """
Controllers Package Code Controllers Package
""" """

View File

@@ -3,16 +3,12 @@
# Lib imports # Lib imports
# Application imports # Application imports
from libs.dto.code import ( from libs.controllers.controller_base import ControllerBase
CodeEvent,
GetCommandSystemEvent, from libs.event_factory import Code_Event_Types
FocusedViewEvent
)
from ..command_system import CommandSystem from ..command_system import CommandSystem
from .controller_base import ControllerBase
class CommandsController(ControllerBase, list): class CommandsController(ControllerBase, list):
@@ -20,8 +16,8 @@ class CommandsController(ControllerBase, list):
super(CommandsController, self).__init__() super(CommandsController, self).__init__()
def _controller_message(self, event: CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, GetCommandSystemEvent): if isinstance(event, Code_Event_Types.GetCommandSystemEvent):
event.response = self.get_command_system() event.response = self.get_command_system()
def get_command_system(self): def get_command_system(self):

View File

@@ -8,19 +8,8 @@ from gi.repository import GLib
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
from libs.dto.code import ( from libs.controllers.controller_base import ControllerBase
CodeEvent, from libs.event_factory import Event_Factory, Code_Event_Types
FocusedViewEvent,
RequestCompletionEvent,
CursorMovedEvent,
TextChangedEvent,
TextInsertedEvent
)
from ..completion_providers.example_completion_provider import ExampleCompletionProvider
from ..completion_providers.lsp_completion_provider import LSPCompletionProvider
from .controller_base import ControllerBase
@@ -28,84 +17,73 @@ class CompletionController(ControllerBase):
def __init__(self): def __init__(self):
super(CompletionController, self).__init__() super(CompletionController, self).__init__()
self._completor: GtkSource.Completion = None self._completers: list[GtkSource.Completion] = []
self._timeout_id: int = None self._providers: dict[str, GtkSource.CompletionProvider] = {}
self._lsp_provider: LSPCompletionProvider = LSPCompletionProvider()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.RegisterProviderEvent):
self.register_provider(
event.provider_name,
event.provider,
event.language_ids
)
elif isinstance(event, Code_Event_Types.AddedNewFileEvent):
self.provider_process_file_load(event)
elif isinstance(event, Code_Event_Types.RemovedFileEvent):
self.provider_process_file_close(event)
elif isinstance(event, Code_Event_Types.SavedFileEvent):
self.provider_process_file_save(event)
elif isinstance(event, Code_Event_Types.TextChangedEvent):
self.provider_process_file_change(event)
# elif isinstance(event, Code_Event_Types.RequestCompletionEvent):
# self.request_unbound_completion( event.view.get_completion() )
def _controller_message(self, event: CodeEvent): def register_completer(self, completer: GtkSource.Completion):
if isinstance(event, FocusedViewEvent): self._completers.append(completer)
self._completor = event.view.get_completion()
if not self._timeout_id: return for provider in self._providers.values():
completer.add_provider(provider)
GLib.source_remove(self._timeout_id) def unregister_completer(self, completer: GtkSource.Completion):
self._timeout_id = None self._completers.remove(completer)
elif isinstance(event, RequestCompletionEvent):
self.request_completion()
# elif isinstance(event, TextInsertedEvent):
# self.request_completion()
def _process_request_completion(self):
self._start_completion()
self._timeout_id = None
return False
def _do_completion(self):
if self._completor.get_providers():
self._match_completion()
else:
self._start_completion()
def _match_completion(self):
"""
Note: Use IF providers were added to completion...
"""
self._completion.match(
self._completion.create_context()
)
def _start_completion(self):
"""
Note: Use IF NO providers have been added to completion...
"""
self._completor.start(
[
ExampleCompletionProvider(),
self._lsp_provider
],
self._completor.create_context()
)
def set_completer(self, completer):
self._completor = completer
def request_completion(self):
if self._timeout_id:
GLib.source_remove(self._timeout_id)
self._timeout_id = GLib.timeout_add(
800,
self._process_request_completion
)
def register_provider( def register_provider(
self, self,
provider_name: str, provider_name: str,
provider: GtkSource.CompletionProvider, provider: GtkSource.CompletionProvider,
priority: int = 0, language_ids: list = []
language_ids: list = None
): ):
"""Register completion providers with priority and language filtering""" self._providers[provider_name] = provider
...
for completer in self._completers:
completer.add_provider(provider)
def unregister_provider(self, provider_name: str): def unregister_provider(self, provider_name: str):
"""Remove completion providers""" provider = self._providers[provider_name]
... del self._providers[provider_name]
def get_active_providers(self, language_id: str = None) -> list: for completer in self._completers:
"""Get providers filtered by language""" completer.remove_provider(provider)
...
def provider_process_file_load(self, event: Code_Event_Types.AddedNewFileEvent):
for provider in self._providers.values():
provider.response_cache.process_file_load(event)
def provider_process_file_close(self, event: Code_Event_Types.RemovedFileEvent):
for provider in self._providers.values():
provider.response_cache.process_file_close(event)
def provider_process_file_save(self, event: Code_Event_Types.SavedFileEvent):
for provider in self._providers.values():
provider.response_cache.process_file_save(event)
def provider_process_file_change(self, event: Code_Event_Types.TextChangedEvent):
for provider in self._providers.values():
provider.response_cache.process_file_change(event)
def request_unbound_completion(self, completer: GtkSource.Completion):
completer.start(
[ *self._providers.values() ],
completer.create_context()
)

View File

@@ -3,13 +3,12 @@
# Lib imports # Lib imports
# Application imports # Application imports
from libs.dto.code import CodeEvent from libs.controllers.controller_base import ControllerBase
from ..event_factory import Event_Factory, Event_Factory_Types from libs.event_factory import Event_Factory, Code_Event_Types
from ..source_file import SourceFile from ..source_file import SourceFile
from ..source_buffer import SourceBuffer from ..source_buffer import SourceBuffer
from .controller_base import ControllerBase
@@ -18,21 +17,21 @@ class FilesController(ControllerBase, list):
super(FilesController, self).__init__() super(FilesController, self).__init__()
def _controller_message(self, event: CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Event_Factory_Types.AddNewFileEvent): if isinstance(event, Code_Event_Types.AddNewFileEvent):
self.new_file(event) self.new_file(event)
elif isinstance(event, Event_Factory_Types.SwapFileEvent): elif isinstance(event, Code_Event_Types.SwapFileEvent):
self.swap_file(event) self.swap_file(event)
elif isinstance(event, Event_Factory_Types.PopFileEvent): elif isinstance(event, Code_Event_Types.PopFileEvent):
self.pop_file(event) self.pop_file(event)
elif isinstance(event, Event_Factory_Types.RemoveFileEvent): elif isinstance(event, Code_Event_Types.RemoveFileEvent):
self.remove_file(event) self.remove_file(event)
elif isinstance(event, Event_Factory_Types.GetFileEvent): elif isinstance(event, Code_Event_Types.GetFileEvent):
self.get_file(event) self.get_file(event)
elif isinstance(event, Event_Factory_Types.GetSwapFileEvent): elif isinstance(event, Code_Event_Types.GetSwapFileEvent):
self.get_swap_file(event) self.get_swap_file(event)
def get_file(self, event: Event_Factory_Types.GetFileEvent): def get_file(self, event: Code_Event_Types.GetFileEvent):
if not event.buffer: return if not event.buffer: return
for file in self: for file in self:
@@ -42,7 +41,7 @@ class FilesController(ControllerBase, list):
return file return file
def get_swap_file(self, event: Event_Factory_Types.GetSwapFileEvent): def get_swap_file(self, event: Code_Event_Types.GetSwapFileEvent):
if not event.buffer: return if not event.buffer: return
for i, file in enumerate(self): for i, file in enumerate(self):
@@ -56,7 +55,7 @@ class FilesController(ControllerBase, list):
return swapped_file, next_file return swapped_file, next_file
def new_file(self, event: Event_Factory_Types.AddNewFileEvent): def new_file(self, event: Code_Event_Types.AddNewFileEvent):
file = SourceFile() file = SourceFile()
file.emit = self.emit file.emit = self.emit
file.emit_to = self.emit_to file.emit_to = self.emit_to
@@ -68,13 +67,13 @@ class FilesController(ControllerBase, list):
view = event.view, view = event.view,
file = file file = file
) )
self.message_all(eve) self.message(eve)
self.append(file) self.append(file)
return file return file
def swap_file(self, event: Event_Factory_Types.GetSwapFileEvent): def swap_file(self, event: Code_Event_Types.GetSwapFileEvent):
if not event.buffer: return if not event.buffer: return
for i, file in enumerate(self): for i, file in enumerate(self):
@@ -88,7 +87,7 @@ class FilesController(ControllerBase, list):
return swapped_file, next_file return swapped_file, next_file
def pop_file(self, event: Event_Factory_Types.PopFileEvent): def pop_file(self, event: Code_Event_Types.PopFileEvent):
if not event.buffer: return if not event.buffer: return
for i, file in enumerate(self): for i, file in enumerate(self):
@@ -106,11 +105,11 @@ class FilesController(ControllerBase, list):
file = popped_file, file = popped_file,
next_file = next_file next_file = next_file
) )
self.message_all(eve) self.message(eve)
return popped_file, next_file return popped_file, next_file
def remove_file(self, event: Event_Factory_Types.RemoveFileEvent): def remove_file(self, event: Code_Event_Types.RemoveFileEvent):
if not event.buffer: return if not event.buffer: return
for i, file in enumerate(self): for i, file in enumerate(self):
@@ -128,7 +127,7 @@ class FilesController(ControllerBase, list):
file = file, file = file,
next_file = next_file next_file = next_file
) )
self.message_all(eve) self.message(eve)
self.remove(file) self.remove(file)
file.close() file.close()

View File

@@ -1,128 +0,0 @@
# Python imports
# Lib imports
# Application imports
from ..event_factory import Event_Factory, Event_Factory_Types
from ..command_system import CommandSystem
from ..key_mapper import KeyMapper
from ..source_view import SourceView
from .controller_base import ControllerBase
class SourceViewsController(ControllerBase, list):
def __init__(self):
super(SourceViewsController, self).__init__()
self.key_mapper: KeyMapper = KeyMapper()
self.active_view: SourceView = None
def get_command_system(self):
event = Event_Factory.create_event("get_command_system")
self.message_to("commands", event)
command = event.response
del event
return command
def create_source_view(self):
source_view: SourceView = SourceView()
source_view.command = self.get_command_system()
source_view.command.set_data(source_view)
self._map_signals(source_view)
self.append(source_view)
return source_view
def _controller_message(self, event: Event_Factory_Types.CodeEvent):
if isinstance(event, Event_Factory_Types.RemovedFileEvent):
self._remove_file(event)
elif isinstance(event, Event_Factory_Types.TextChangedEvent):
self.active_view.command.exec("update_info_bar")
def _map_signals(self, source_view: SourceView):
source_view.connect("focus-in-event", self._focus_in_event)
source_view.connect("move-cursor", self._move_cursor)
source_view.connect("key-press-event", self._key_press_event)
source_view.connect("key-release-event", self._key_release_event)
source_view.connect("button-press-event", self._button_press_event)
source_view.connect("button-release-event", self._button_release_event)
def _focus_in_event(self, view, eve):
self.active_view = view
view.command.exec("set_miniview")
view.command.exec("set_focus_border")
view.command.exec("update_info_bar")
event = Event_Factory.create_focused_view(view = view)
self.emit(event)
def _move_cursor(self, view, step, count, extend_selection):
buffer = view.get_buffer()
iter = buffer.get_iter_at_mark( buffer.get_insert() )
line = iter.get_line()
char = iter.get_line_offset()
event = Event_Factory.create_cursor_moved(
view = view,
buffer = buffer,
line = line,
char = char
)
self.emit(event)
view.command.exec("update_info_bar")
def _button_press_event(self, view, eve):
self.active_view.command.exec("update_info_bar")
def _button_release_event(self, view, eve):
self.active_view.command.exec("update_info_bar")
def _key_press_event(self, view, eve):
command = self.key_mapper._key_press_event(eve)
is_future = self.key_mapper._key_release_event(eve)
if is_future: return True
if not command: return False
view.command.exec(command)
return True
def _key_release_event(self, view, eve):
command = self.key_mapper._key_release_event(eve)
is_past = self.key_mapper._key_press_event(eve)
if is_past: return True
if not command: return False
view.command.exec(command)
return True
def _remove_file(self, event: Event_Factory_Types.RemovedFileEvent):
for view in self:
if not event.file.buffer == view.get_buffer(): continue
if not event.next_file:
view.command.exec("new_file")
continue
view.set_buffer(event.next_file.buffer)
def first_map_load(self):
for view in self:
view.command.exec("new_file")
view = self[0]
view.grab_focus()
view.command.exec("load_start_files")

View File

@@ -1,18 +1,19 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
from gi.repository import Gtk
# Application imports # Application imports
from libs.dto.code import CodeEvent from libs.controllers.controller_base import ControllerBase
from ..event_factory import Event_Factory, Event_Factory_Types from libs.event_factory import Event_Factory, Code_Event_Types
from ..tabs_widget import TabsWidget from ..tabs_widget import TabsWidget
from ..tab_widget import TabWidget from ..tab_widget import TabWidget
from ..source_view import SourceView from ..source_view import SourceView
from .controller_base import ControllerBase
class TabsController(ControllerBase): class TabsController(ControllerBase):
@@ -21,66 +22,54 @@ class TabsController(ControllerBase):
self.active_view: SourceView = None self.active_view: SourceView = None
self.tabs_widget: TabsWidget = TabsWidget() self.tabs_widget: TabsWidget = TabsWidget()
self.tabs_widget.message = self.message
def _controller_message(self, event: CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Event_Factory_Types.FocusedViewEvent): if isinstance(event, Code_Event_Types.FocusedViewEvent):
self.active_view = event.view self.active_view = event.view
elif isinstance(event, Event_Factory_Types.FilePathSetEvent): self.tabs_widget.view_changed(
event.view.get_buffer()
)
elif isinstance(event, Code_Event_Types.FilePathSetEvent):
self.update_tab_label(event) self.update_tab_label(event)
elif isinstance(event, Event_Factory_Types.AddedNewFileEvent): elif isinstance(event, Code_Event_Types.AddedNewFileEvent):
self.add_tab(event) self.add_tab(event)
elif isinstance(event, Event_Factory_Types.PoppedFileEvent): elif isinstance(event, Code_Event_Types.PoppedFileEvent):
... ...
elif isinstance(event, Event_Factory_Types.RemovedFileEvent): elif isinstance(event, Code_Event_Types.RemovedFileEvent):
self.remove_tab(event) self.remove_tab(event)
def get_tabs_widget(self): def get_tabs_widget(self):
return self.tabs_widget return self.tabs_widget
def update_tab_label(self, event: Event_Factory_Types.FilePathSetEvent): def update_tab_label(self, event: Code_Event_Types.FilePathSetEvent):
for tab in self.tabs_widget.get_children(): for page_widget in self.tabs_widget.get_children():
tab = self.tabs_widget.get_tab_label(page_widget)
if not event.file == tab.file: continue if not event.file == tab.file: continue
tab.label.set_label(event.file.fname) tab.label.set_label(event.file.fname)
break break
def add_tab(self, event: Event_Factory_Types.AddedNewFileEvent): def add_tab(self, event: Code_Event_Types.AddedNewFileEvent):
def set_active_tab(tab, eve, file): box = Gtk.Separator()
event = Event_Factory.create_event(
"set_active_file",
buffer = tab.get_parent().file.buffer
)
self.active_view.set_buffer(
tab.get_parent().file.buffer
)
self.message_all(event)
def close_tab(tab, eve, file):
event = Event_Factory.create_event(
"remove_file",
buffer = tab.get_parent().file.buffer
)
self.message_all(event)
tab = TabWidget() tab = TabWidget()
tab.file = event.file tab.file = event.file
tab.label.set_label(event.file.fname) tab.label.set_label(event.file.fname)
tab.set_select_signal(set_active_tab)
tab.set_close_signal(close_tab)
self.tabs_widget.add(tab) self.tabs_widget.append_page(box, tab)
tab.show() tab.show_all()
def remove_tab(self, event: Event_Factory_Types.RemovedFileEvent): def remove_tab(self, event: Code_Event_Types.RemovedFileEvent):
for tab in self.tabs_widget.get_children(): for page_widget in self.tabs_widget.get_children():
tab = self.tabs_widget.get_tab_label(page_widget)
if not event.file == tab.file: continue if not event.file == tab.file: continue
tab.clear_signals_and_data() tab.clear_signals_and_data()
tab.run_dispose() self.tabs_widget.remove_page(
tab.destroy() self.tabs_widget.page_num(page_widget)
)
del tab
break break

View File

@@ -0,0 +1,10 @@
"""
Code Controllers Package
"""
from .state_manager import SourceViewStateManager
from .signal_mapper import SourceViewSignalMapper
from .source_views_controller import SourceViewsController
# State imports
from .states import *

View File

@@ -0,0 +1,63 @@
# Python imports
# Lib imports
# Application imports
from ...source_view import SourceView
class SourceViewSignalMapper:
def __init__(self):
self.active_view: SourceView = None
def bind_emit(self, emit: callable):
self.emit = emit
def set_state_manager(self, state_manager):
self.state_manager = state_manager
def connect_signals(self, source_view: SourceView):
signal_mappings = self._get_signal_mappings()
for signal, handler in signal_mappings.items():
source_view.connect(signal, handler)
def disconnect_signals(self, source_view: SourceView):
signal_mappings = self._get_signal_mappings()
for signal, handler in signal_mappings.items():
source_view.disconnect_by_func(handler)
def insert_text(self, file, string: str):
return self.state_manager.handle_insert_text(self.active_view, file, string)
def _get_signal_mappings(self):
return {
"focus-in-event": self._focus_in_event,
"move-cursor": self._move_cursor,
"key-press-event": self._key_press_event,
"key-release-event": self._key_release_event,
"button-press-event": self._button_press_event,
"button-release-event": self._button_release_event
}
def _focus_in_event(self, source_view: SourceView, eve):
self.active_view = source_view
return self.state_manager.handle_focus_in_event(source_view, eve, self.emit)
def _move_cursor(self, source_view: SourceView, step, count, extend_selection):
return self.state_manager.handle_move_cursor(
source_view, step, count, extend_selection, self.emit
)
def _button_press_event(self, source_view: SourceView, eve):
return self.state_manager.handle_button_press_event(source_view, eve)
def _button_release_event(self, source_view: SourceView, eve):
return self.state_manager.handle_button_release_event(source_view, eve)
def _key_press_event(self, source_view: SourceView, eve):
return self.state_manager.handle_key_press_event(source_view, eve)
def _key_release_event(self, source_view: SourceView, eve):
return self.state_manager.handle_key_release_event(source_view, eve)

View File

@@ -0,0 +1,76 @@
# Python imports
# Lib imports
# Application imports
from libs.controllers.controller_base import ControllerBase
from libs.event_factory import Event_Factory, Code_Event_Types
from ...source_view import SourceView
from .state_manager import SourceViewStateManager
from .signal_mapper import SourceViewSignalMapper
class SourceViewsController(ControllerBase, list):
def __init__(self):
super(SourceViewsController, self).__init__()
self.state_manager: SourceViewStateManager = SourceViewStateManager()
self.signal_mapper: SourceViewSignalMapper = SourceViewSignalMapper()
self.signal_mapper.bind_emit(self.emit)
self.signal_mapper.set_state_manager(self.state_manager)
def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.RemovedFileEvent):
self._remove_file(event)
if not self.signal_mapper.active_view: return
if isinstance(event, Code_Event_Types.TextChangedEvent):
if not self.signal_mapper.active_view: return
self.signal_mapper.active_view.command.exec("update_info_bar")
elif isinstance(event, Code_Event_Types.SetActiveFileEvent):
self.signal_mapper.active_view.set_buffer(
event.buffer
)
elif isinstance(event, Code_Event_Types.TextInsertedEvent):
self.signal_mapper.insert_text(event.file, event.text)
def _get_command_system(self):
event = Event_Factory.create_event("get_command_system")
self.message_to("commands", event)
command = event.response
del event
return command
def _remove_file(self, event: Code_Event_Types.RemovedFileEvent):
for source_view in self:
if not event.file.buffer == source_view.get_buffer(): continue
if not event.next_file:
source_view.command.exec("new_file")
continue
source_view.set_buffer(event.next_file.buffer)
def create_source_view(self):
source_view: SourceView = SourceView()
source_view.command = self._get_command_system()
source_view.command.set_data(source_view)
self.signal_mapper.connect_signals(source_view)
self.append(source_view)
return source_view
def first_map_load(self):
for source_view in self:
source_view.command.exec("new_file")
source_view = self[0]
source_view.grab_focus()
source_view.command.exec("load_start_files")

View File

@@ -0,0 +1,65 @@
# Python imports
# Lib imports
# Application imports
from libs.dto.states import SourceViewStates
from ...key_mapper import KeyMapper
from .states import *
class SourceViewStateManager:
def __init__(self):
self.key_mapper: KeyMapper = KeyMapper()
self.states: dict = {
SourceViewStates.INSERT: SourceViewsInsertState(),
SourceViewStates.MULTIINSERT: SourceViewsMultiInsertState(),
SourceViewStates.COMMAND: SourceViewsCommandState(),
SourceViewStates.READONLY: SourceViewsReadOnlyState()
}
def handle_focus_in_event(self, source_view, eve, emit):
return self.states[source_view.state].focus_in_event(source_view, eve, emit)
def handle_insert_text(self, source_view, file, text):
return self.states[source_view.state].insert_text(file, text)
def handle_move_cursor(self, source_view, step, count, extend_selection, emit):
return self.states[source_view.state].move_cursor(
source_view, step, count, extend_selection, emit
)
def handle_button_press_event(self, source_view, eve):
return self.states[source_view.state].button_press_event(source_view, eve)
def handle_button_release_event(self, source_view, eve):
# Handle state transitions (multi-insert toggling)
self._handle_multi_insert_toggle(source_view, eve)
return self.states[source_view.state].button_release_event(source_view, eve)
def handle_key_press_event(self, source_view, eve):
return self.states[source_view.state].key_press_event(
source_view, eve, self.key_mapper
)
def handle_key_release_event(self, source_view, eve):
return self.states[source_view.state].key_release_event(
source_view, eve, self.key_mapper
)
def _handle_multi_insert_toggle(self, source_view, eve):
is_control = self.key_mapper.is_control(eve)
if is_control and not source_view.state == SourceViewStates.MULTIINSERT:
logger.debug("Entered Multi-Insert Mode...")
source_view.state = SourceViewStates.MULTIINSERT
if not is_control and source_view.state == SourceViewStates.MULTIINSERT:
logger.debug("Entered Regular Insert Mode...")
self.states[source_view.state].clear_markers(source_view)
source_view.state = SourceViewStates.INSERT

View File

@@ -0,0 +1,8 @@
"""
Code Controllers Views States Package
"""
from .source_view_insert_state import SourceViewsInsertState
from .source_view_multi_insert_state import SourceViewsMultiInsertState
from .source_view_command_state import SourceViewsCommandState
from .source_view_read_only_state import SourceViewsReadOnlyState

View File

@@ -0,0 +1,36 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from libs.dto.states import SourceViewStates
class SourceViewsCommandState:
def __init__(self):
super(SourceViewsCommandState, self).__init__()
def focus_in_event(self, source_view, eve, emit):
return True
def move_cursor(self, source_view, step, count, extend_selection, emit):
return True
def insert_text(self, file, text):
return True
def button_press_event(self, source_view, eve):
return True
def button_release_event(self, source_view, eve):
return True
def key_press_event(self, source_view, eve, key_mapper):
return True
def key_release_event(self, source_view, eve, key_mapper):
return True

View File

@@ -0,0 +1,71 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from libs.dto.states import SourceViewStates
class SourceViewsInsertState:
def __init__(self):
super(SourceViewsInsertState, self).__init__()
def focus_in_event(self, source_view, eve, emit):
source_view.command.exec("set_miniview")
source_view.command.exec("set_focus_border")
source_view.command.exec("update_info_bar")
event = Event_Factory.create_event("focused_view", view = source_view)
emit(event)
def insert_text(self, file, text):
return True
def move_cursor(self, source_view, step, count, extend_selection, emit):
buffer = source_view.get_buffer()
itr = buffer.get_iter_at_mark( buffer.get_insert() )
line = itr.get_line()
char = itr.get_line_offset()
event = Event_Factory.create_event(
"cursor_moved",
view = source_view,
buffer = buffer,
line = line,
char = char
)
emit(event)
source_view.command.exec("update_info_bar")
def button_press_event(self, source_view, eve):
source_view.command.exec("update_info_bar")
def button_release_event(self, source_view, eve):
source_view.command.exec("update_info_bar")
def key_press_event(self, source_view, eve, key_mapper):
command = key_mapper._key_press_event(eve)
is_future = key_mapper._key_release_event(eve)
if is_future: return True
if not command: return False
source_view.command.exec(command)
return True
def key_release_event(self, source_view, eve, key_mapper):
command = key_mapper._key_release_event(eve)
is_past = key_mapper._key_press_event(eve)
if is_past: return True
if not command: return False
source_view.command.exec(command)
return True

View File

@@ -0,0 +1,160 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from libs.dto.states import SourceViewStates, MoveDirection, CursorAction
from ....mixins.source_mark_events_mixin import MarkEventsMixin
class SourceViewsMultiInsertState(MarkEventsMixin):
def __init__(self):
super(SourceViewsMultiInsertState, self).__init__()
self.cursor_action: CursorAction = 0
self.move_direction: MoveDirection = 0
self.insert_markers: list = []
def focus_in_event(self, source_view, eve, emit):
source_view.command.exec("set_miniview")
source_view.command.exec("set_focus_border")
source_view.command.exec("update_info_bar")
event = Event_Factory.create_event("focused_view", view = source_view)
emit(event)
def insert_text(self, file, text):
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()
buffer.begin_user_action()
with buffer.freeze_notify():
for mark in self.insert_markers:
itr = buffer.get_iter_at_mark(mark)
buffer.insert(itr, text, -1)
buffer.end_user_action()
buffer.unblock_insert_after_signal()
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()
self._process_move_direction(buffer)
self._signal_cursor_moved(source_view, emit)
source_view.command.exec("update_info_bar")
def button_press_event(self, source_view, eve):
source_view.command.exec("update_info_bar")
return True
def button_release_event(self, source_view, eve):
buffer = source_view.get_buffer()
insert_iter = buffer.get_iter_at_mark( buffer.get_insert() )
data = source_view.window_to_buffer_coords(
Gtk.TextWindowType.TEXT,
eve.x,
eve.y
)
is_over_text, \
target_iter, \
is_trailing = source_view.get_iter_at_position(data.buffer_x, data.buffer_y)
if not is_over_text:
# NOTE: Trying to put at very end of line if not over text (aka, clicking right of text)
target_iter.forward_visible_line()
target_iter.backward_char()
self._insert_mark(insert_iter, target_iter, buffer)
def key_press_event(self, source_view, eve, key_mapper):
char = key_mapper.get_raw_keyname(eve)
for action in CursorAction:
if not action.name == char.upper(): continue
self.cursor_action = action.value
self._process_cursor_action(source_view.get_buffer())
return False
for direction in MoveDirection:
if not direction.name == char.upper(): continue
self.move_direction = direction.value
return False
is_future = key_mapper._key_release_event(eve)
if is_future: return True
command = key_mapper._key_press_event(eve)
if not command: return False
source_view.command.exec(command)
return True
def key_release_event(self, source_view, eve, key_mapper):
command = key_mapper._key_release_event(eve)
is_past = key_mapper._key_press_event(eve)
if is_past: return True
if not command: return False
source_view.command.exec(command)
return True
def _signal_cursor_moved(self, source_view, emit):
buffer = source_view.get_buffer()
itr = buffer.get_iter_at_mark( buffer.get_insert() )
line = itr.get_line()
char = itr.get_line_offset()
event = Event_Factory.create_event(
"cursor_moved",
view = source_view,
buffer = buffer,
line = line,
char = char
)
emit(event)

View File

@@ -0,0 +1,36 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from libs.dto.states import SourceViewStates
class SourceViewsReadOnlyState:
def __init__(self):
super(SourceViewsReadOnlyState, self).__init__()
def focus_in_event(self, source_view, eve, emit):
return True
def move_cursor(self, source_view, step, count, extend_selection, emit):
return True
def insert_text(self, file, text):
return True
def button_press_event(self, source_view, eve):
return True
def button_release_event(self, source_view, eve):
return True
def key_press_event(self, source_view, eve, key_mapper):
return True
def key_release_event(self, source_view, eve, key_mapper):
return True

View File

@@ -1,101 +0,0 @@
# Python imports
import inspect
from typing import Dict, Type
import re
# Lib imports
# Application imports
from libs.singleton import Singleton
from libs.dto.code import CodeEvent
from libs.dto import code
class EventFactory(Singleton):
def __init__(self):
self._event_classes: Dict[str, Type[CodeEvent]] = {}
self._auto_register_events()
def register_event(self, event_type: str, event_class: Type[CodeEvent]):
self._event_classes[event_type] = event_class
def create_event(self, event_type: str, **kwargs) -> CodeEvent:
if event_type not in self._event_classes:
raise ValueError(f"Unknown event type: {event_type}")
event_class = self._event_classes[event_type]
event = event_class()
for key, value in kwargs.items():
if hasattr(event, key):
setattr(event, key, value)
else:
raise ValueError(f"Event class {event_class.__name__} has no attribute '{key}'")
return event
def _auto_register_events(self):
for name, obj in code.__dict__.items():
if not self._is_valid_event_class(obj): continue
event_type = self._class_name_to_event_type(name)
self.register_event(event_type, obj)
logger.debug(f"Auto-registered {len(self._event_classes)} event types")
def _is_valid_event_class(self, obj) -> bool:
return (inspect.isclass(obj) and
issubclass(obj, CodeEvent) and
obj != CodeEvent)
def _class_name_to_event_type(self, class_name: str) -> str:
base_name = class_name[:-5] if class_name.endswith('Event') else class_name
return re.sub(r'(?<!^)(?=[A-Z])', '_', base_name).lower()
def create_cursor_moved(self, **kwargs):
return self.create_event("cursor_moved", **kwargs)
def create_text_changed(self, **kwargs):
return self.create_event("text_changed", **kwargs)
def create_focused_view(self, **kwargs):
return self.create_event("focused_view", **kwargs)
def create_modified_changed(self, **kwargs):
return self.create_event("modified_changed", **kwargs)
def create_get_command_system(self, **kwargs):
return self.create_event("get_command_system", **kwargs)
def create_file_path_set(self, **kwargs):
return self.create_event("file_path_set", **kwargs)
def create_text_inserted(self, **kwargs):
return self.create_event("text_inserted", **kwargs)
def create_set_active_file(self, **kwargs):
return self.create_event("set_active_file", **kwargs)
def create_added_new_file(self, **kwargs):
return self.create_event("added_new_file", **kwargs)
def create_popped_file(self, **kwargs):
return self.create_event("popped_file", **kwargs)
def create_get_file(self, **kwargs):
return self.create_event("get_file", **kwargs)
def create_get_swap_file(self, **kwargs):
return self.create_event("get_swap_file", **kwargs)
def create_remove_file(self, **kwargs):
return self.create_event("remove_file", **kwargs)
def create_removed_file(self, **kwargs):
return self.create_event("removed_file", **kwargs)
Event_Factory = EventFactory()
Event_Factory_Types = code

View File

@@ -125,3 +125,13 @@ class KeyMapper:
if is_alt: if is_alt:
self.state = self.state | AltKeyState self.state = self.state | AltKeyState
def is_control(self, eve):
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
return True if modifiers & Gdk.ModifierType.CONTROL_MASK else False
def is_shift(self, eve):
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
return True if modifiers & Gdk.ModifierType.SHIFT_MASK else False
def get_raw_keyname(self, eve):
return Gdk.keyval_name(eve.keyval)

Some files were not shown because too many files have changed in this diff Show More