Compare commits

...

3 Commits

79 changed files with 3431 additions and 844 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

@@ -24,11 +24,14 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin)
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()
@@ -62,13 +65,19 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin)
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 == "true": return
if args.no_plugins == "false": if is_pre:
self.plugins_controller.pre_launch_plugins() self.plugins_controller.pre_launch_plugins()
self.plugins_controller.post_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)

View File

@@ -11,7 +11,7 @@ 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
@@ -50,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

@@ -39,11 +39,11 @@ class CommandSystem:
def emit(self, event: Code_Event_Types.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: Code_Event_Types.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... """
... ...

View File

@@ -15,4 +15,14 @@ def execute(
view: GtkSource.View = None view: GtkSource.View = None
): ):
logger.debug("Command: Show Completion") logger.debug("Command: Show Completion")
completer = view.get_completion()
providers = completer.get_providers()
if not providers:
view.command.request_completion(view) view.command.request_completion(view)
return
completer.start(
providers,
completer.create_context()
)

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

@@ -11,93 +11,79 @@ from gi.repository import GtkSource
from libs.controllers.controller_base import ControllerBase from libs.controllers.controller_base import ControllerBase
from libs.event_factory import Event_Factory, Code_Event_Types from libs.event_factory import Event_Factory, Code_Event_Types
from ..completion_providers.example_completion_provider import ExampleCompletionProvider
from ..completion_providers.lsp_completion_provider import LSPCompletionProvider
class CompletionController(ControllerBase): 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): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.FocusedViewEvent): if isinstance(event, Code_Event_Types.RegisterProviderEvent):
self._completor = event.view.get_completion() self.register_provider(
event.provider_name,
if not self._timeout_id: return event.provider,
event.language_ids
GLib.source_remove(self._timeout_id)
self._timeout_id = None
elif isinstance(event, Code_Event_Types.RequestCompletionEvent):
self.request_completion()
# elif isinstance(event, Code_Event_Types.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()
) )
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 _start_completion(self): def register_completer(self, completer: GtkSource.Completion):
""" self._completers.append(completer)
Note: Use IF NO providers have been added to completion...
"""
self._completor.start(
[
ExampleCompletionProvider(),
self._lsp_provider
],
self._completor.create_context()
)
for provider in self._providers.values():
completer.add_provider(provider)
def set_completer(self, completer): def unregister_completer(self, completer: GtkSource.Completion):
self._completor = completer self._completers.remove(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

@@ -1,128 +0,0 @@
# Python imports
# Lib imports
# Application imports
from libs.controllers.controller_base import ControllerBase
from libs.event_factory import Event_Factory, Code_Event_Types
from ..command_system import CommandSystem
from ..key_mapper import KeyMapper
from ..source_view import SourceView
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: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.RemovedFileEvent):
self._remove_file(event)
elif isinstance(event, Code_Event_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_event("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_event(
"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: Code_Event_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,6 +1,9 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
from gi.repository import Gtk
# Application imports # Application imports
from libs.controllers.controller_base import ControllerBase from libs.controllers.controller_base import ControllerBase
@@ -19,11 +22,15 @@ 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: Code_Event_Types.CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.FocusedViewEvent): if isinstance(event, Code_Event_Types.FocusedViewEvent):
self.active_view = event.view self.active_view = event.view
self.tabs_widget.view_changed(
event.view.get_buffer()
)
elif isinstance(event, Code_Event_Types.FilePathSetEvent): elif isinstance(event, Code_Event_Types.FilePathSetEvent):
self.update_tab_label(event) self.update_tab_label(event)
elif isinstance(event, Code_Event_Types.AddedNewFileEvent): elif isinstance(event, Code_Event_Types.AddedNewFileEvent):
@@ -37,48 +44,32 @@ class TabsController(ControllerBase):
return self.tabs_widget return self.tabs_widget
def update_tab_label(self, event: Code_Event_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: Code_Event_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(event)
def close_tab(tab, eve, file):
event = Event_Factory.create_event(
"remove_file",
buffer = tab.get_parent().file.buffer
)
self.message(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: Code_Event_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

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

View File

@@ -0,0 +1,107 @@
# Python imports
import random
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from libs.dto.states import SourceViewStates, MoveDirection, CursorAction
class MarkEventsMixin:
def clear_markers(self, source_view):
buffer = source_view.get_buffer()
for mark in self.insert_markers:
mark.set_visible(False)
buffer.delete_mark(mark)
self.insert_markers.clear()
def _insert_mark(self, insert_iter, target_iter, buffer):
mark_found = self._check_for_insert_marks(target_iter, buffer)
if mark_found: return
random_bits = random.getrandbits(128)
hash = "%032x" % random_bits
mark = Gtk.TextMark.new(
name = f"multi_insert_{hash}",
left_gravity = False
)
buffer.add_mark(mark, target_iter)
mark.set_visible(True)
self.insert_markers.append(mark)
def _check_for_insert_marks(self, target_iter, buffer):
marks = target_iter.get_marks()
for mark in marks:
if mark in self.insert_markers[:]:
mark.set_visible(False)
self.insert_markers.remove(mark)
buffer.delete_mark(mark)
return True
insert_itr = buffer.get_iter_at_mark( buffer.get_insert() )
if target_iter.equal(insert_itr): return True
return False
def _process_cursor_action(self, buffer):
if not self.insert_markers: return
if self.cursor_action == CursorAction.NONE.value: return
action = self.cursor_action
for mark in self.insert_markers:
itr = buffer.get_iter_at_mark(mark)
if action == CursorAction.BACKSPACE.value:
buffer.backspace(itr, interactive = True, default_editable = True)
elif action == CursorAction.DELETE.value:
itr.forward_char()
buffer.backspace(itr, interactive = True, default_editable = True)
elif action == CursorAction.ENTER.value:
...
self.cursor_action = CursorAction.NONE.value
def _process_move_direction(self, buffer):
if not self.insert_markers: return
if self.move_direction == MoveDirection.NONE.value: return
direction = self.move_direction
for mark in self.insert_markers:
itr = buffer.get_iter_at_mark(mark)
if direction == MoveDirection.UP.value:
new_line = itr.get_line() - 1
new_itr = buffer.get_iter_at_line_offset(
new_line,
itr.get_line_index()
)
elif direction == MoveDirection.DOWN.value:
new_line = itr.get_line() + 1
new_itr = buffer.get_iter_at_line_offset(
new_line,
itr.get_line_index()
)
elif direction == MoveDirection.LEFT.value:
if not itr.backward_char(): break
new_itr = itr
elif direction == MoveDirection.RIGHT.value:
if not itr.forward_char(): break
new_itr = itr
else:
continue
buffer.move_mark_by_name(mark.get_name(), new_itr)
self.move_direction = MoveDirection.NONE

View File

@@ -13,6 +13,8 @@ class SourceBuffer(GtkSource.Buffer):
def __init__(self): def __init__(self):
super(SourceBuffer, self).__init__() super(SourceBuffer, self).__init__()
self.is_processing_completion: bool = False
self._handler_ids = [] self._handler_ids = []
@@ -21,6 +23,7 @@ class SourceBuffer(GtkSource.Buffer):
_changed, _changed,
_mark_set, _mark_set,
_insert_text, _insert_text,
_after_insert_text,
_modified_changed, _modified_changed,
): ):
@@ -28,9 +31,28 @@ class SourceBuffer(GtkSource.Buffer):
self.connect("changed", _changed), self.connect("changed", _changed),
self.connect("mark-set", _mark_set), self.connect("mark-set", _mark_set),
self.connect("insert-text", _insert_text), self.connect("insert-text", _insert_text),
self.connect_after("insert-text", _after_insert_text),
self.connect("modified-changed", _modified_changed) self.connect("modified-changed", _modified_changed)
] ]
def block_changed_signal(self):
self.handler_block(self._handler_ids[0])
def block_insert_after_signal(self):
self.handler_block(self._handler_ids[3])
def block_modified_changed_signal(self):
self.handler_block(self._handler_ids[4])
def unblock_changed_signal(self):
self.handler_unblock(self._handler_ids[0])
def unblock_insert_after_signal(self):
self.handler_unblock(self._handler_ids[3])
def unblock_modified_changed_signal(self):
self.handler_unblock(self._handler_ids[4])
def clear_signals(self): def clear_signals(self):
for handle_id in self._handler_ids: for handle_id in self._handler_ids:
self.disconnect(handle_id) self.disconnect(handle_id)
@@ -38,3 +60,4 @@ class SourceBuffer(GtkSource.Buffer):
def __del__(self): def __del__(self):
for handle_id in self._handler_ids: for handle_id in self._handler_ids:
self.disconnect(handle_id) self.disconnect(handle_id)

View File

@@ -8,6 +8,7 @@ gi.require_version('Gtk', '3.0')
gi.require_version('GtkSource', '4') gi.require_version('GtkSource', '4')
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import GtkSource from gi.repository import GtkSource
from gi.repository import Gio from gi.repository import Gio
@@ -37,6 +38,7 @@ class SourceFile(GtkSource.File):
self._changed, self._changed,
self._mark_set, self._mark_set,
self._insert_text, self._insert_text,
self._after_insert_text,
self._modified_changed self._modified_changed
) )
@@ -45,17 +47,38 @@ class SourceFile(GtkSource.File):
event.file = self event.file = self
self.emit(event) self.emit(event)
def _insert_text(self, buffer: SourceBuffer, location: Gtk.TextIter, def _insert_text(
self,
buffer: SourceBuffer,
location: Gtk.TextIter,
text: str, length: int
):
...
def _after_insert_text(
self,
buffer: SourceBuffer,
location: Gtk.TextIter,
text: str, length: int text: str, length: int
): ):
event = Event_Factory.create_event( event = Event_Factory.create_event(
"text_inserted", "text_inserted",
file = self, file = self,
buffer = buffer buffer = self.buffer,
location = location,
text = text,
length = length
) )
self.emit(event)
def _mark_set(self, buffer: SourceBuffer, location: Gtk.TextIter, # Note: 'idle_add' needed b/c markers don't get thir positions
# updated relative to the initial insert.
# If not used, seg faults galor during multi insert.
GLib.idle_add(self.emit, event)
def _mark_set(
self,
buffer: SourceBuffer,
location: Gtk.TextIter,
mark: Gtk.TextMark mark: Gtk.TextMark
): ):
# event = Event_Factory.create_event( # event = Event_Factory.create_event(
@@ -108,6 +131,12 @@ class SourceFile(GtkSource.File):
self.emit(event) self.emit(event)
def save(self): def save(self):
event = Event_Factory.create_event(
"saved_file",
file = self, buffer = self.buffer
)
self.emit(event)
self._write_file( self.get_location() ) self._write_file( self.get_location() )
def save_as(self): def save_as(self):

View File

@@ -10,14 +10,18 @@ from gi.repository import GLib
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
from libs.dto.states import SourceViewStates
from .mixins.source_view_dnd_mixin import SourceViewDnDMixin from .mixins.source_view_dnd_mixin import SourceViewDnDMixin
class SourceView(GtkSource.View, SourceViewDnDMixin): class SourceView(GtkSource.View, SourceViewDnDMixin):
def __init__(self): def __init__(self, state: SourceViewStates = SourceViewStates.INSERT):
super(SourceView, self).__init__() super(SourceView, self).__init__()
self.state = state
self._cut_temp_timeout_id = None self._cut_temp_timeout_id = None
self._cut_buffer = "" self._cut_buffer = ""

View File

@@ -10,52 +10,62 @@ from gi.repository import Gtk
class TabWidget(Gtk.Box): class TabWidget(Gtk.Box):
"""docstring for TabWidget"""
def __init__(self): def __init__(self):
super(TabWidget, self).__init__() super(TabWidget, self).__init__()
self.file = None self._close_tab = None
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show_all()
def _setup_styling(self): def _setup_styling(self):
ctx = self.get_style_context() ctx = self.get_style_context()
ctx.add_class("tab-widget") ctx.add_class("tab-widget")
self.set_orientation(0)
self.set_hexpand(False)
def _setup_signals(self): def _setup_signals(self):
... ...
def _subscribe_to_events(self):
...
def _load_widgets(self): def _load_widgets(self):
self._label_eve_box = Gtk.EventBox() self.label = Gtk.Label()
self.label = Gtk.Label(label = "") self.close_btn = Gtk.Button()
self.close_btn = Gtk.Button(label = "X") icon = Gtk.Image(stock = Gtk.STOCK_CLOSE)
ctx = self.label.get_style_context() ctx = self.label.get_style_context()
ctx.add_class("tab-label") ctx.add_class("tab-label")
ctx = self.close_btn.get_style_context() ctx = self.close_btn.get_style_context()
ctx.add_class("tab-close-bttn") ctx.add_class("tab-close-bttn")
self.label.set_xalign(0.0)
self.label.set_margin_left(25)
self.label.set_margin_right(25)
self.label.set_hexpand(True) self.label.set_hexpand(True)
self._label_eve_box.add(self.label) self.close_btn.add(icon)
self.add(self._label_eve_box) self.add(self.label)
self.add(self.close_btn) self.add(self.close_btn)
def clear_signals_and_data(self): self.show_all()
del self.file
self._label_eve_box.disconnect(self._label_eve_box_id)
self.close_btn.disconnect(self.close_btn_id)
def set_select_signal(self, callback): def clear_signals_and_data(self):
self._label_eve_box_id = self._label_eve_box.connect('button-release-event', callback, self.file) self.close_btn.disconnect(self._handler_id)
self._close_tab = None
self._handler_id = None
for child in self.get_children():
child.unparent()
child.run_dispose()
child.destroy()
def set_close_signal(self, callback): def set_close_signal(self, callback):
self.close_btn_id = self.close_btn.connect('button-release-event', callback, self.file) self._handler_id = self.close_btn.connect(
'button-release-event',
callback,
self.file
)

View File

@@ -6,16 +6,13 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from libs.event_factory import Code_Event_Types from libs.event_factory import Event_Factory, Code_Event_Types
from .source_view import SourceView
from .source_file import SourceFile
from .tab_widget import TabWidget from .tab_widget import TabWidget
class TabsWidget(Gtk.ButtonBox): class TabsWidget(Gtk.Notebook):
def __init__(self): def __init__(self):
super(TabsWidget, self).__init__() super(TabsWidget, self).__init__()
@@ -26,10 +23,12 @@ class TabsWidget(Gtk.ButtonBox):
def _setup_styling(self): def _setup_styling(self):
self.set_layout(Gtk.ButtonBoxStyle.CENTER) ...
def _setup_signals(self): def _setup_signals(self):
... self.connect("page-added", self._page_added)
self.switch_page_id = \
self.connect_after("switch-page", self._switch_page)
def _subscribe_to_events(self): def _subscribe_to_events(self):
... ...
@@ -37,51 +36,40 @@ class TabsWidget(Gtk.ButtonBox):
def _load_widgets(self): def _load_widgets(self):
... ...
def add_tab(self, event: Code_Event_Types.CodeEvent): def _page_added(self, notebook, page_widget, page_num):
"""Add a tab widget for the given file event.""" tab = self.get_tab_label(page_widget)
if not hasattr(self, 'tabs'): tab.set_close_signal(self._close_tab)
return
tab = TabWidget() page_widget.show()
tab.file = event.file self.set_tab_detachable(page_widget, True)
self.set_tab_reorderable(page_widget, True)
tab.label.set_label(event.file.fname) def _close_tab(self, tab, eve, file):
event = Event_Factory.create_event(
def select_signal(widget, eve, file): "remove_file",
self.code_base.active_view.command.exec_with_args( buffer = tab.get_parent().file.buffer
"set_buffer",
(self.code_base.active_view, file)
) )
def close_signal(widget, eve, file): self.message(event)
self.code_base.files_controller.remove_file(file.buffer)
tab.set_select_signal(select_signal) def _switch_page(self, notebook, page_widget, page_num):
tab.set_close_signal(close_signal) tab = self.get_tab_label(page_widget)
event = Event_Factory.create_event(
"set_active_file",
buffer = tab.file.buffer
)
self.tabs.add(tab) self.message(event)
def remove_tab(self, event: Code_Event_Types.CodeEvent): def view_changed(self, buffer):
"""Remove a tab widget for the given file event.""" for page_widget in self.get_children():
if not hasattr(self, 'tabs'): tab = self.get_tab_label(page_widget)
return if not buffer == tab.file.buffer: continue
for child in self.tabs.get_children(): self.handler_block(self.switch_page_id)
if not child.file == event.file: continue
self.tabs.remove(child) self.set_current_page(
child.clear_signals_and_data() self.page_num(page_widget)
del child )
return self.handler_unblock(self.switch_page_id)
def update_tab_label(self, event: Code_Event_Types.CodeEvent):
"""Update tab label for the given file event."""
if not hasattr(self, 'tabs'):
return
for tab in self.tabs.get_children():
if not tab.file == event.file: continue
tab.label.set_label(event.file.fname)
return

View File

@@ -44,6 +44,7 @@ class WebkitUI(WebKit2.WebView):
data = f.read() data = f.read()
self.load_html(content = data, base_uri = f"file://{path}/") self.load_html(content = data, base_uri = f"file://{path}/")
# self.load_uri("https://duckduckgo.com/")
def _setup_content_manager(self): def _setup_content_manager(self):
content_manager = self.get_user_content_manager() content_manager = self.get_user_content_manager()

View File

@@ -3,7 +3,7 @@
# Lib imports # Lib imports
# Application imports # Application imports
from libs.singleton import Singleton from ..singleton import Singleton
from ..dto.base_event import BaseEvent from ..dto.base_event import BaseEvent
@@ -25,7 +25,7 @@ class ControllerBase(Singleton, EmitDispatcher):
def _controller_message(self, event: BaseEvent): def _controller_message(self, event: BaseEvent):
raise ControllerBaseException("Controller Base must override '_controller_message'...") raise ControllerBaseException("Controller Base '_controller_message' must be overridden...")
def set_controller_context(self, controller_context: ControllerContext): def set_controller_context(self, controller_context: ControllerContext):
self.controller_context = controller_context self.controller_context = controller_context

View File

@@ -4,6 +4,8 @@
from .code_event import CodeEvent from .code_event import CodeEvent
from .register_provider_event import RegisterProviderEvent
from .get_command_system_event import GetCommandSystemEvent from .get_command_system_event import GetCommandSystemEvent
from .request_completion_event import RequestCompletionEvent from .request_completion_event import RequestCompletionEvent
from .cursor_moved_event import CursorMovedEvent from .cursor_moved_event import CursorMovedEvent
@@ -18,6 +20,7 @@ from .added_new_file_event import AddedNewFileEvent
from .swapped_file_event import SwappedFileEvent from .swapped_file_event import SwappedFileEvent
from .popped_file_event import PoppedFileEvent from .popped_file_event import PoppedFileEvent
from .removed_file_event import RemovedFileEvent from .removed_file_event import RemovedFileEvent
from .saved_file_event import SavedFileEvent
from .get_file_event import GetFileEvent from .get_file_event import GetFileEvent
from .get_swap_file_event import GetSwapFileEvent from .get_swap_file_event import GetSwapFileEvent

View File

@@ -0,0 +1,19 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
import gi
gi.require_version('GtkSource', '4')
from gi.repository import GtkSource
# Application imports
from ..base_event import BaseEvent
@dataclass
class RegisterProviderEvent(BaseEvent):
provider_name: str = ""
provider: GtkSource.CompletionProvider = None
language_ids: list = field(default_factory=lambda: [])

View File

@@ -0,0 +1,13 @@
# Python imports
from dataclasses import dataclass, field
# Lib imports
# Application imports
from .code_event import CodeEvent
@dataclass
class SavedFileEvent(CodeEvent):
...

View File

@@ -2,6 +2,11 @@
from dataclasses import dataclass, field from dataclasses import dataclass, field
# Lib imports # Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports # Application imports
from .code_event import CodeEvent from .code_event import CodeEvent
@@ -10,6 +15,6 @@ from .code_event import CodeEvent
@dataclass @dataclass
class TextInsertedEvent(CodeEvent): class TextInsertedEvent(CodeEvent):
line: int = 0 location: Gtk.TextIter = None
char: int = 0 text: str = ""
value: str = "" length: int = 0

View File

@@ -0,0 +1,7 @@
"""
Code DTO States Package
"""
from .source_view_states import SourceViewStates
from .cursor_action import CursorAction
from .move_direction import MoveDirection

View File

@@ -0,0 +1,14 @@
# Python imports
from enum import Enum
# Lib imports
# Application imports
class CursorAction(Enum):
NONE = 0
DELETE = 1
BACKSPACE = 2
ENTER = 3

View File

@@ -0,0 +1,15 @@
# Python imports
from enum import Enum
# Lib imports
# Application imports
class MoveDirection(Enum):
NONE = 0
UP = 1
DOWN = 2
LEFT = 3
RIGHT = 4

View File

@@ -0,0 +1,14 @@
# Python imports
from enum import Enum
# Lib imports
# Application imports
class SourceViewStates(Enum):
INSERT = 0
MULTIINSERT = 1
COMMAND = 2
READONLY = 3

View File

@@ -39,4 +39,5 @@ class WebkitUISettings(WebKit2.Settings):
self.set_enable_webaudio(True) self.set_enable_webaudio(True)
self.set_enable_accelerated_2d_canvas(True) self.set_enable_accelerated_2d_canvas(True)
self.set_user_agent(f"{APP_NAME}") self.set_user_agent(f"Mozilla/5.0 (macOS, AArch64) {APP_NAME}/1.0 Chrome/140.0.0 AppleWebKit/537.36 Safari/537.36")

View File

@@ -55,7 +55,7 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
for file in os.listdir(path): for file in os.listdir(path):
_path = os.path.join(path, file) _path = os.path.join(path, file)
if os.path.isdir(_path): if os.path.isdir(_path):
self.collect_search_locations(_path, locations) self._collect_search_locations(_path, locations)
def _load_plugins( def _load_plugins(
self, self,
@@ -70,7 +70,7 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
try: try:
target = join(path, "plugin.py") target = join(path, "plugin.py")
if not os.path.exists(target): if not os.path.exists(target):
raise InvalidPluginException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...") raise PluginsControllerException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
module = self._load_plugin_module(path, folder, target) module = self._load_plugin_module(path, folder, target)

View File

@@ -32,11 +32,8 @@ class ManifestManager:
for path, folder in [ for path, folder in [
[join(self._plugins_path, item), item] [join(self._plugins_path, item), item]
if
os.path.isdir( join(self._plugins_path, item) )
else
None
for item in os.listdir(self._plugins_path) for item in os.listdir(self._plugins_path)
if os.path.isdir( join(self._plugins_path, item) )
]: ]:
self.load(folder, path) self.load(folder, path)

View File

@@ -0,0 +1,7 @@
"""
Plugin Types Module
"""
from .plugin_base import PluginBase
from .plugin_ui import PluginUI
from .plugin_code import PluginCode

View File

@@ -0,0 +1,49 @@
# Python imports
# Lib imports
# Application imports
from libs.dto.base_event import BaseEvent
from ..plugin_context import PluginContext
class PluginBaseException(Exception):
...
class PluginBase:
def __init__(self, *args, **kwargs):
super(PluginBase, self).__init__(*args, **kwargs)
self.plugin_context: PluginContext = None
def _controller_message(self, event: BaseEvent):
raise PluginBaseException("Plugin Base '_controller_message' must be overriden by Plugin")
def load(self):
raise PluginBaseException("Plugin Base 'load' must be overriden by Plugin")
def run(self):
raise PluginBaseException("Plugin Base 'run' must be overriden by Plugin")
def requests_ui_element(self, element_id: str):
raise PluginBaseException("Plugin Base 'requests_ui_element' must be overriden by Plugin")
def message(self, event: BaseEvent):
raise PluginBaseException("Plugin Base 'message' must be overriden by Plugin")
def message_to(self, name: str, event: BaseEvent):
raise PluginBaseException("Plugin Base 'message_to' must be overriden by Plugin")
def message_to_selected(self, names: list[str], event: BaseEvent):
raise PluginBaseException("Plugin Base 'message_to_selected' must be overriden by Plugin")
def emit(self, event_type: str, data: tuple = ()):
raise PluginBaseException("Plugin Base 'emit' must be overriden by Plugin")
def emit_and_await(self, event_type: str, data: tuple = ()):
raise PluginBaseException("Plugin Base 'emit_and_await' must be overriden by Plugin")

View File

@@ -0,0 +1,41 @@
# Python imports
# Lib imports
# Application imports
from libs.dto.base_event import BaseEvent
from ..plugin_context import PluginContext
from .plugin_base import PluginBase
class PluginCodeException(Exception):
...
class PluginCode(PluginBase):
def __init__(self, *args, **kwargs):
super(PluginCode, self).__init__(*args, **kwargs)
self.plugin_context: PluginContext = None
def _controller_message(self, event: BaseEvent):
raise PluginCodeException("Plugin Code '_controller_message' must be overriden by Plugin")
def load(self):
raise PluginCodeException("Plugin Code 'load' must be overriden by Plugin")
def run(self):
raise PluginCodeException("Plugin Code 'run' must be overriden by Plugin")
def message(self, event: BaseEvent):
return self.plugin_context.message(event)
def message_to(self, name: str, event: BaseEvent):
return self.plugin_context.message_to(name, event)
def message_to_selected(self, names: list[str], event: BaseEvent):
return self.plugin_context.message_to_selected(names, event)

View File

@@ -5,30 +5,31 @@
# Application imports # Application imports
from libs.dto.base_event import BaseEvent from libs.dto.base_event import BaseEvent
from .plugin_context import PluginContext from ..plugin_context import PluginContext
from .plugin_base import PluginBase
class PluginBaseException(Exception): class PluginCodeException(Exception):
... ...
class PluginBase: class PluginUI(PluginBase):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(PluginBase, self).__init__(*args, **kwargs) super(PluginUI, self).__init__(*args, **kwargs)
self.plugin_context: PluginContext = None self.plugin_context: PluginContext = None
def _controller_message(self, event: BaseEvent): def _controller_message(self, event: BaseEvent):
raise PluginBaseException("Plugin Base '_controller_message' must be overriden by Plugin") raise PluginCodeException("Plugin UI '_controller_message' must be overriden by Plugin")
def load(self): def load(self):
raise PluginBaseException("Plugin Base 'load' must be overriden by Plugin") raise PluginCodeException("Plugin UI 'load' must be overriden by Plugin")
def run(self): def run(self):
raise PluginBaseException("Plugin Base 'run' must be overriden by Plugin") raise PluginCodeException("Plugin UI 'run' must be overriden by Plugin")
def requests_ui_element(self, element_id: str): def requests_ui_element(self, element_id: str):
return self.plugin_context.requests_ui_element(element_id) return self.plugin_context.requests_ui_element(element_id)

View File

@@ -6,6 +6,10 @@
class InvalidPluginException(Exception):
...
class PluginsControllerMixin: class PluginsControllerMixin:

View File

@@ -1,21 +1,56 @@
/* ---- Make most desired things base transparent ---- */ /* ---- Overrides ---- */
text selection {
background-color: #44475a;
color: #FFA500;
}
/* ---- Make most desired things base transparent ---- */ /* ---- Make most desired things base transparent ---- */
popover, popover,
popover > box, popover > box,
notebook, notebook,
header, header,
stack, stack {
scrolledwindow,
viewport {
background: rgba(0, 0, 0, 0.0); background: rgba(0, 0, 0, 0.0);
color: rgba(255, 255, 255, 1); color: rgba(255, 255, 255, 1);
} }
scrolledwindow * {
background: rgba(0, 0, 0, 0.0); /* The scrolled window itself */
scrolledwindow,
scrolledwindow viewport,
textview,
textview text {
background-color: transparent;
} }
scrollbar {
background-color: #2a2a2a;
}
/* The track */
scrollbar trough {
background-color: transparent;
/*background-color: #2a2a2a;*/
border-radius: 6px;
}
/* The draggable handle */
scrollbar slider {
background-color: #555;
border-radius: 6px;
min-width: 10px;
min-height: 10px;
}
scrollbar slider:hover {
background-color: #777;
}
scrollbar slider:active {
background-color: #999;
}
border { border {
background: rgba(39, 43, 52, 0.84); background: rgba(39, 43, 52, 0.84);
} }
@@ -41,6 +76,20 @@ tab:checked {
color: rgba(255, 255, 255, 1); color: rgba(255, 255, 255, 1);
} }
/* the mini view container of text */
.mini-view > text {
background: rgba(39, 43, 52, 0.64);
}
/* draggable overlay of the miniview */
.mini-view > * {
background-color: rgba(124, 124, 124, 0.34);
color: rgba(255, 255, 255, 1);
font-size: 1px;
}
.base-container { .base-container {
margin: 10px; margin: 10px;
} }
@@ -96,177 +145,9 @@ tab:checked {
color: rgba(255, 0, 0, 0.64); color: rgba(255, 0, 0, 0.64);
} }
.error-txt { color: rgb(170, 18, 18); }
.warning-txt { color: rgb(255, 168, 0); }
.success-txt { color: rgb(136, 204, 39); }
/*popover,*/
/*popover > box*/
/*.main-window > box,*/
/*.main-window > box > box,*/
/*.main-window > box > box > paned,*/
/*.main-window > box > box > paned > notebook,*/
/*.main-window > box > box > paned > notebook > stack,*/
/*.main-window > box > box > paned > notebook > stack > scrolledwindow > textview > * {*/
/* background: rgba(0, 0, 0, 0.0);*/
/* color: rgba(255, 255, 255, 1);*/
/*}*/
/* ---- top controls ---- */
/*.main-window > box > box > button,*/
/*.main-window > box > box > buttonbox > button {*/
/* background: rgba(39, 43, 52, 0.64);*/
/* margin-left: 12px;*/
/* margin-right: 12px;*/
/*}*/
/* status bar */
/*.main-window > box > statusbar {*/
/* background: rgba(39, 43, 52, 0.64);*/
/* padding-left: 96px;*/
/* padding-right: 96px;*/
/*}*/
/* ---- notebook headers ---- */
/*notebook > header {*/
/* background: rgba(39, 43, 52, 0.72);*/
/*}*/
/*notebook > header > tabs > tab {*/
/* color: rgba(255, 255, 255, 1);*/
/*}*/
/*notebook > header > tabs > tab:active {*/
/* background: rgba(0, 0, 0, 0.0);*/
/*}*/
/* ---- notebook tab buttons ---- */
/*tab > box > button {*/
/* background: rgba(116, 0, 0, 0.64);*/
/*}*/
/*tab > box > button:hover {*/
/* background: rgba(256, 0, 0, 0.64);*/
/*}*/
/*notebook > header > tabs > tab:checked {*/
/* Neon Blue 00e8ff */
/* background-color: rgba(0, 232, 255, 0.2); */
/* background-color: rgba(255, 255, 255, 0.46);*/
/* Dark Bergundy */
/* background-color: rgba(116, 0, 0, 0.25); */
/* color: rgba(255, 255, 255, 0.8);*/
/*}*/
/*.notebook-selected-focus {*/
/* Neon Blue 00e8ff border */
/* border: 2px solid rgba(0, 232, 255, 0.34); */
/* border: 2px solid rgba(255, 255, 255, 0.46);*/
/* Dark Bergundy */
/* border: 2px solid rgba(116, 0, 0, 0.64); */
/*}*/
/* ---- source code notebooks ---- */
/*notebook > stack > scrolledwindow > textview {*/
/* background: rgba(39, 43, 52, 0.64);*/
/* color: rgba(255, 255, 255, 1);*/
/*}*/
/*textview {*/
/* padding-bottom: 50em;*/
/*}*/
/* any popover */
/*popover {*/
/* background: rgba(39, 43, 52, 0.86);*/
/* color: rgba(255, 255, 255, 1);*/
/*}*/
/* ---- make text selection slightly transparent ---- */
/** selection {*/
/* background-color: rgba(0, 115, 115, 0.34);*/
/* Bergundy */
/* background-color: rgba(116, 0, 0, 0.64); */
/* color: rgba(255, 255, 255, 0.5);*/
/*}*/
/* ---- miniview properties ---- */
/*.source-view,*/
/*.mini-view {*/
/* font-family: Monospace;*/
/*}*/
/* the mini view container of text */
/*.mini-view > text {*/
/* background: rgba(39, 43, 52, 0.64);*/
/*}*/
/* draggable overlay of the miniview */
/*.mini-view > * {*/
/* background: rgba(64, 64, 64, 0.32);*/
/* color: rgba(255, 255, 255, 1);*/
/* font-size: 1px;*/
/*}*/
/*.mini-view > text {*/
/* background: rgba(39, 43, 52, 0.64);*/
/*}*/
/* other properties */
/*.buffer_changed {*/
/* color: rgba(255, 168, 0, 1.0);*/
/* color: rgba(0, 232, 255, 0.64); */
/*}*/
/*.searching,*/
/*.search_success,*/
/*.search_fail {*/
/* border-style: solid;*/
/*}*/
/*.searching {*/
/* border-color: rgba(0, 225, 225, 0.64);*/
/*}*/
/*.search_success {*/
/* background: rgba(136, 204, 39, 0.12);*/
/* border-color: rgba(136, 204, 39, 1);*/
/*}*/
/*.search_fail {*/
/* background: rgba(170, 18, 18, 0.12);*/
/* border-color: rgba(200, 18, 18, 1);*/
/*}*/
.error_txt { color: rgb(170, 18, 18); }
.warning_txt { color: rgb(255, 168, 0); }
.success_txt { color: rgb(136, 204, 39); }