diff --git a/plugins/lsp_completer/__init__.py b/plugins/lsp_completer/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/lsp_completer/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/lsp_completer/__main__.py b/plugins/lsp_completer/__main__.py new file mode 100644 index 0000000..a576329 --- /dev/null +++ b/plugins/lsp_completer/__main__.py @@ -0,0 +1,3 @@ +""" + Pligin Package +""" diff --git a/plugins/lsp_completer/manifest.json b/plugins/lsp_completer/manifest.json new file mode 100644 index 0000000..6557297 --- /dev/null +++ b/plugins/lsp_completer/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "LSP Completer", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": {} +} diff --git a/plugins/lsp_completer/plugin.py b/plugins/lsp_completer/plugin.py new file mode 100644 index 0000000..7de452e --- /dev/null +++ b/plugins/lsp_completer/plugin.py @@ -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): + ... diff --git a/plugins/lsp_completer/provider.py b/plugins/lsp_completer/provider.py new file mode 100644 index 0000000..305d057 --- /dev/null +++ b/plugins/lsp_completer/provider.py @@ -0,0 +1,79 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('GtkSource', '4') + +from gi.repository import GtkSource +from gi.repository import GObject + +# Application imports +from .provider_response_cache import ProviderResponseCache + + + +class Provider(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.response_cache: ProviderResponseCache = ProviderResponseCache() + + + 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): + word = self.response_cache.get_word(context) + if not word or len(word) < 2: return False + + iter = self.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_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): + proposals = self.get_completion_filter(context) + + context.add_proposals(self, proposals, True) + + def get_completion_filter(self, context): + proposals = [ + self.response_cache.create_completion_item( + "LSP Class", + "LSP Code", + "A test LSP completion item..." + ) + ] + + return proposals diff --git a/plugins/lsp_completer/provider_response_cache.py b/plugins/lsp_completer/provider_response_cache.py new file mode 100644 index 0000000..54b827d --- /dev/null +++ b/plugins/lsp_completer/provider_response_cache.py @@ -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): + ... + + def filter_with_context(self, context: GtkSource.CompletionContext): + proposals = [ + self.create_completion_item( + "LSP Class", + "LSP Code", + "A test LSP completion item..." + ) + ] + + return proposals diff --git a/plugins/snippets_completer/__init__.py b/plugins/snippets_completer/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/snippets_completer/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/snippets_completer/__main__.py b/plugins/snippets_completer/__main__.py new file mode 100644 index 0000000..a576329 --- /dev/null +++ b/plugins/snippets_completer/__main__.py @@ -0,0 +1,3 @@ +""" + Pligin Package +""" diff --git a/plugins/snippets_completer/cson/__init__.py b/plugins/snippets_completer/cson/__init__.py new file mode 100644 index 0000000..7eaf7bf --- /dev/null +++ b/plugins/snippets_completer/cson/__init__.py @@ -0,0 +1,3 @@ +from .parser import load, loads +from .writer import dump, dumps +from .speg import ParseError diff --git a/plugins/snippets_completer/cson/parser.py b/plugins/snippets_completer/cson/parser.py new file mode 100644 index 0000000..b8e4e98 --- /dev/null +++ b/plugins/snippets_completer/cson/parser.py @@ -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 diff --git a/plugins/snippets_completer/cson/speg/__init__.py b/plugins/snippets_completer/cson/speg/__init__.py new file mode 100644 index 0000000..d3ebb8f --- /dev/null +++ b/plugins/snippets_completer/cson/speg/__init__.py @@ -0,0 +1 @@ +from .peg import peg, ParseError diff --git a/plugins/snippets_completer/cson/speg/peg.py b/plugins/snippets_completer/cson/speg/peg.py new file mode 100644 index 0000000..15bd1d1 --- /dev/null +++ b/plugins/snippets_completer/cson/speg/peg.py @@ -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__ diff --git a/plugins/snippets_completer/cson/writer.py b/plugins/snippets_completer/cson/writer.py new file mode 100644 index 0000000..de856a4 --- /dev/null +++ b/plugins/snippets_completer/cson/writer.py @@ -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) diff --git a/plugins/snippets_completer/manifest.json b/plugins/snippets_completer/manifest.json new file mode 100644 index 0000000..669678e --- /dev/null +++ b/plugins/snippets_completer/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Snippets Completer", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": {} +} diff --git a/plugins/snippets_completer/plugin.py b/plugins/snippets_completer/plugin.py new file mode 100644 index 0000000..100c641 --- /dev/null +++ b/plugins/snippets_completer/plugin.py @@ -0,0 +1,40 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from libs.dto.base_event import BaseEvent +from libs.event_factory import Event_Factory + +from plugins.plugin_types import PluginCode + +from .provider import Provider + + + +class Plugin(PluginCode): + def __init__(self): + super(Plugin, self).__init__() + + self.provider: Provider = None + + + def _controller_message(self, event: BaseEvent): + ... + + def load(self): + self.provider = Provider() + + event = Event_Factory.create_event( + "register_provider", + provider_name = "Snippets Completer", + provider = self.provider, + language_ids = [] + ) + self.message_to("completion", event) + + def run(self): + ... diff --git a/plugins/snippets_completer/provider.py b/plugins/snippets_completer/provider.py new file mode 100644 index 0000000..9ae0c45 --- /dev/null +++ b/plugins/snippets_completer/provider.py @@ -0,0 +1,63 @@ +# 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(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): + GObject.Object.__init__(self) + + 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_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) diff --git a/plugins/snippets_completer/provider_response_cache.py b/plugins/snippets_completer/provider_response_cache.py new file mode 100644 index 0000000..656d703 --- /dev/null +++ b/plugins/snippets_completer/provider_response_cache.py @@ -0,0 +1,63 @@ +# 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): + response: list = [] + + for entry in self.matchers: + if not word in entry: continue + data = self.matchers[entry] + response.append(data) + + return response diff --git a/plugins/snippets_completer/snippets.cson b/plugins/snippets_completer/snippets.cson new file mode 100644 index 0000000..7f99623 --- /dev/null +++ b/plugins/snippets_completer/snippets.cson @@ -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': """ + + + + + + + + + + + + + + + + + + + +""" + + 'Canvas Tag': + 'prefix': 'canvas' + 'body': """""" + + 'Img Tag': + 'prefix': 'img' + 'body': """""" + + 'Br Tag': + 'prefix': 'br' + 'body': """
""" + + 'Hr Tag': + 'prefix': 'hr' + 'body': """
""" + + '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 === "") { + // 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 === "") { + // 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 Template': + 'prefix': 'php' + 'body': """ Illegal Access Method!"; + serverMessage("error", $message); +} +?> +""" + 'HTML Template': + 'prefix': 'html' + 'body': """ + + + + + + + + + + + + + + + + + + + +""" + + +### 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('') + 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): + ... + + """ diff --git a/src/core/controllers/base_controller.py b/src/core/controllers/base_controller.py index 3aeb5cb..c95c9da 100644 --- a/src/core/controllers/base_controller.py +++ b/src/core/controllers/base_controller.py @@ -24,11 +24,14 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin) def __init__(self): self._setup_controller_data() + + self._load_plugins(is_pre = True) self._setup_styling() self._setup_signals() self._subscribe_to_events() 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...") settings_manager.set_end_load_time() @@ -62,13 +65,19 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerMixin) def _load_controllers(self): BridgeController() - def _load_plugins_and_files(self): + def _load_plugins(self, is_pre: bool): 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.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(): event_system.emit("post-file-to-ipc", file) diff --git a/src/core/widgets/code/code_base.py b/src/core/widgets/code/code_base.py index 1b5dbb0..8013c08 100644 --- a/src/core/widgets/code/code_base.py +++ b/src/core/widgets/code/code_base.py @@ -11,7 +11,7 @@ from .controllers.files_controller import FilesController from .controllers.tabs_controller import TabsController from .controllers.commands_controller import CommandsController 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 @@ -50,7 +50,12 @@ class CodeBase: return self.miniview_widget 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): self.controller_manager["source_views"].first_map_load() diff --git a/src/core/widgets/code/command_system/command_system.py b/src/core/widgets/code/command_system/command_system.py index 63986f2..48af7ed 100644 --- a/src/core/widgets/code/command_system/command_system.py +++ b/src/core/widgets/code/command_system/command_system.py @@ -39,11 +39,11 @@ class CommandSystem: 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): - """ Monky patch 'emit' from command controller... """ + """ Monkey patch 'emit_to' from command controller... """ ... diff --git a/src/core/widgets/code/command_system/commands/show_completion.py b/src/core/widgets/code/command_system/commands/show_completion.py index dedb491..baec540 100644 --- a/src/core/widgets/code/command_system/commands/show_completion.py +++ b/src/core/widgets/code/command_system/commands/show_completion.py @@ -15,4 +15,14 @@ def execute( view: GtkSource.View = None ): logger.debug("Command: Show Completion") - view.command.request_completion(view) + completer = view.get_completion() + providers = completer.get_providers() + + if not providers: + view.command.request_completion(view) + return + + completer.start( + providers, + completer.create_context() + ) diff --git a/src/core/widgets/code/completion_providers/example_completion_provider.py b/src/core/widgets/code/completion_providers/example_completion_provider.py deleted file mode 100644 index 10f29a0..0000000 --- a/src/core/widgets/code/completion_providers/example_completion_provider.py +++ /dev/null @@ -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) \ No newline at end of file diff --git a/src/core/widgets/code/completion_providers/lsp_completion_provider.py b/src/core/widgets/code/completion_providers/lsp_completion_provider.py deleted file mode 100644 index 45b76ba..0000000 --- a/src/core/widgets/code/completion_providers/lsp_completion_provider.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/widgets/code/completion_providers/provider_response_cache_base.py b/src/core/widgets/code/completion_providers/provider_response_cache_base.py new file mode 100644 index 0000000..0ecce4d --- /dev/null +++ b/src/core/widgets/code/completion_providers/provider_response_cache_base.py @@ -0,0 +1,87 @@ +# 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): + raise ProviderResponseCacheException("ProviderResponseCacheBase 'filter' not implemented...") + + def filter_with_context(self, context: GtkSource.CompletionContext): + raise ProviderResponseCacheException("ProviderResponseCacheBase 'filter_with_context' not implemented...") + + + def create_completion_item( + self, + label: str = "", + text: str = "", + info: str = "", + completion: any = None + ): + 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"

{info}

") + + if completion: + comp_item.set_icon( + self.get_icon_for_type(completion.type) + ) + + return comp_item + + def get_word(self, context): + start_iter = context.get_iter() + end_iter = None + + if not isinstance(start_iter, Gtk.TextIter): + _, start_iter = context.get_iter() + end_iter = start_iter.copy() + + if not start_iter.starts_word(): + start_iter.backward_word_start() + + end_iter.forward_word_end() + + buffer = start_iter.get_buffer() + + return buffer.get_text(start_iter, end_iter, False) diff --git a/src/core/widgets/code/completion_providers/python_completion_provider.py b/src/core/widgets/code/completion_providers/python_completion_provider.py deleted file mode 100644 index 319ce99..0000000 --- a/src/core/widgets/code/completion_providers/python_completion_provider.py +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/widgets/code/controllers/__init__.py b/src/core/widgets/code/controllers/__init__.py index e614b70..8f3b747 100644 --- a/src/core/widgets/code/controllers/__init__.py +++ b/src/core/widgets/code/controllers/__init__.py @@ -1,3 +1,3 @@ """ Code Controllers Package -""" \ No newline at end of file +""" diff --git a/src/core/widgets/code/controllers/completion_controller.py b/src/core/widgets/code/controllers/completion_controller.py index 6330841..91dec63 100644 --- a/src/core/widgets/code/controllers/completion_controller.py +++ b/src/core/widgets/code/controllers/completion_controller.py @@ -11,93 +11,84 @@ from gi.repository import GtkSource from libs.controllers.controller_base import ControllerBase 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): def __init__(self): super(CompletionController, self).__init__() - self._completor: GtkSource.Completion = None - self._timeout_id: int = None - self._lsp_provider: LSPCompletionProvider = LSPCompletionProvider() + self.words_provider = GtkSource.CompletionWords.new("words", None) + self.words_provider.props.activation = GtkSource.CompletionActivation.INTERACTIVE + self._completers: list[GtkSource.Completion] = [] + self._providers: dict[str, GtkSource.CompletionProvider] = {} def _controller_message(self, event: Code_Event_Types.CodeEvent): - if isinstance(event, Code_Event_Types.FocusedViewEvent): - self._completor = event.view.get_completion() - - if not self._timeout_id: return - - 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() - ) + if isinstance(event, Code_Event_Types.RegisterProviderEvent): + self.register_provider( + event.provider_name, + event.provider, + event.language_ids + ) + elif isinstance(event, Code_Event_Types.AddedNewFileEvent): + self.provider_process_file_load(event) + elif isinstance(event, Code_Event_Types.RemovedFileEvent): + self.provider_process_file_close(event) + elif isinstance(event, Code_Event_Types.SavedFileEvent): + self.provider_process_file_save(event) + elif isinstance(event, Code_Event_Types.TextChangedEvent): + self.provider_process_file_change(event) + # elif isinstance(event, Code_Event_Types.RequestCompletionEvent): + # self.request_unbound_completion( event.view.get_completion() ) - def _start_completion(self): - """ - Note: Use IF NO providers have been added to completion... - """ - self._completor.start( - [ - ExampleCompletionProvider(), - self._lsp_provider - ], - self._completor.create_context() - ) + def register_completer(self, completer: GtkSource.Completion): + self._completers.append(completer) - - def set_completer(self, completer): - self._completor = completer - - def request_completion(self): - if self._timeout_id: - GLib.source_remove(self._timeout_id) - - self._timeout_id = GLib.timeout_add( - 800, - self._process_request_completion - ) + completer.add_provider(self.words_provider) + for provider in self._providers.values(): + completer.add_provider(provider) def register_provider( self, provider_name: str, - provider: GtkSource.CompletionProvider, - priority: int = 0, - language_ids: list = None + provider: GtkSource.CompletionProvider, + language_ids: list = [] ): - """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): - """Remove completion providers""" - ... - - def get_active_providers(self, language_id: str = None) -> list: - """Get providers filtered by language""" - ... + provider = self._providers[provider_name] + del self._providers[provider_name] + + for completer in self._completers: + completer.remove_provider(provider) + + def provider_process_file_load(self, event: Code_Event_Types.AddedNewFileEvent): + self.words_provider.register(event.file.buffer) + + for provider in self._providers.values(): + provider.response_cache.process_file_load(event) + + def provider_process_file_close(self, event: Code_Event_Types.RemovedFileEvent): + self.words_provider.unregister(event.file.buffer) + + 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() + ) diff --git a/src/core/widgets/code/controllers/source_views_controller.py b/src/core/widgets/code/controllers/source_views_controller.py deleted file mode 100644 index c2ff44d..0000000 --- a/src/core/widgets/code/controllers/source_views_controller.py +++ /dev/null @@ -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") - diff --git a/src/core/widgets/code/controllers/views/__init__.py b/src/core/widgets/code/controllers/views/__init__.py new file mode 100644 index 0000000..1461e87 --- /dev/null +++ b/src/core/widgets/code/controllers/views/__init__.py @@ -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 * \ No newline at end of file diff --git a/src/core/widgets/code/controllers/views/signal_mapper.py b/src/core/widgets/code/controllers/views/signal_mapper.py new file mode 100644 index 0000000..8577f57 --- /dev/null +++ b/src/core/widgets/code/controllers/views/signal_mapper.py @@ -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) diff --git a/src/core/widgets/code/controllers/views/source_views_controller.py b/src/core/widgets/code/controllers/views/source_views_controller.py new file mode 100644 index 0000000..08c914e --- /dev/null +++ b/src/core/widgets/code/controllers/views/source_views_controller.py @@ -0,0 +1,74 @@ +# 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.TextChangedEvent): + self.signal_mapper.active_view.command.exec("update_info_bar") + 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") diff --git a/src/core/widgets/code/controllers/views/state_manager.py b/src/core/widgets/code/controllers/views/state_manager.py new file mode 100644 index 0000000..8bae1a1 --- /dev/null +++ b/src/core/widgets/code/controllers/views/state_manager.py @@ -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 diff --git a/src/core/widgets/code/controllers/views/states/__init__.py b/src/core/widgets/code/controllers/views/states/__init__.py new file mode 100644 index 0000000..9b79f5c --- /dev/null +++ b/src/core/widgets/code/controllers/views/states/__init__.py @@ -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 diff --git a/src/core/widgets/code/controllers/views/states/source_view_command_state.py b/src/core/widgets/code/controllers/views/states/source_view_command_state.py new file mode 100644 index 0000000..d57d808 --- /dev/null +++ b/src/core/widgets/code/controllers/views/states/source_view_command_state.py @@ -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 diff --git a/src/core/widgets/code/controllers/views/states/source_view_insert_state.py b/src/core/widgets/code/controllers/views/states/source_view_insert_state.py new file mode 100644 index 0000000..7a2bd66 --- /dev/null +++ b/src/core/widgets/code/controllers/views/states/source_view_insert_state.py @@ -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 diff --git a/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py b/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py new file mode 100644 index 0000000..8de34be --- /dev/null +++ b/src/core/widgets/code/controllers/views/states/source_view_multi_insert_state.py @@ -0,0 +1,131 @@ +# 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 + + # 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 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 False + + 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 False + 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) diff --git a/src/core/widgets/code/controllers/views/states/source_view_read_only_state.py b/src/core/widgets/code/controllers/views/states/source_view_read_only_state.py new file mode 100644 index 0000000..07317d2 --- /dev/null +++ b/src/core/widgets/code/controllers/views/states/source_view_read_only_state.py @@ -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 diff --git a/src/core/widgets/code/key_mapper.py b/src/core/widgets/code/key_mapper.py index 9ddfb96..9193401 100644 --- a/src/core/widgets/code/key_mapper.py +++ b/src/core/widgets/code/key_mapper.py @@ -125,3 +125,13 @@ class KeyMapper: if is_alt: 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) diff --git a/src/core/widgets/code/mixins/source_mark_events_mixin.py b/src/core/widgets/code/mixins/source_mark_events_mixin.py new file mode 100644 index 0000000..460d70c --- /dev/null +++ b/src/core/widgets/code/mixins/source_mark_events_mixin.py @@ -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 diff --git a/src/core/widgets/code/source_buffer.py b/src/core/widgets/code/source_buffer.py index 8c70296..083d15c 100644 --- a/src/core/widgets/code/source_buffer.py +++ b/src/core/widgets/code/source_buffer.py @@ -21,20 +21,41 @@ class SourceBuffer(GtkSource.Buffer): _changed, _mark_set, _insert_text, + _after_insert_text, _modified_changed, ): self._handler_ids = [ - self.connect("changed", _changed), - self.connect("mark-set", _mark_set), - self.connect("insert-text", _insert_text), - self.connect("modified-changed", _modified_changed) + self.connect("changed", _changed), + self.connect("mark-set", _mark_set), + self.connect("insert-text", _insert_text), + self.connect_after("insert-text", _after_insert_text), + 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): for handle_id in self._handler_ids: self.disconnect(handle_id) def __del__(self): for handle_id in self._handler_ids: - self.disconnect(handle_id) \ No newline at end of file + self.disconnect(handle_id) + diff --git a/src/core/widgets/code/source_file.py b/src/core/widgets/code/source_file.py index 02817c1..43288af 100644 --- a/src/core/widgets/code/source_file.py +++ b/src/core/widgets/code/source_file.py @@ -8,6 +8,7 @@ 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 from gi.repository import Gio @@ -37,6 +38,7 @@ class SourceFile(GtkSource.File): self._changed, self._mark_set, self._insert_text, + self._after_insert_text, self._modified_changed ) @@ -45,17 +47,38 @@ class SourceFile(GtkSource.File): event.file = self 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 ): event = Event_Factory.create_event( "text_inserted", - file = self, - buffer = buffer + file = self, + 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 ): # event = Event_Factory.create_event( @@ -108,6 +131,12 @@ class SourceFile(GtkSource.File): self.emit(event) def save(self): + event = Event_Factory.create_event( + "saved_file", + file = self, buffer = self.buffer + ) + + self.emit(event) self._write_file( self.get_location() ) def save_as(self): diff --git a/src/core/widgets/code/source_view.py b/src/core/widgets/code/source_view.py index ce48f6b..bbb752d 100644 --- a/src/core/widgets/code/source_view.py +++ b/src/core/widgets/code/source_view.py @@ -10,14 +10,18 @@ from gi.repository import GLib from gi.repository import GtkSource # Application imports +from libs.dto.states import SourceViewStates + from .mixins.source_view_dnd_mixin import SourceViewDnDMixin class SourceView(GtkSource.View, SourceViewDnDMixin): - def __init__(self): + def __init__(self, state: SourceViewStates = SourceViewStates.INSERT): super(SourceView, self).__init__() + self.state = state + self._cut_temp_timeout_id = None self._cut_buffer = "" diff --git a/src/core/widgets/webkit/webkit_ui.py b/src/core/widgets/webkit/webkit_ui.py index d6476c2..749a717 100644 --- a/src/core/widgets/webkit/webkit_ui.py +++ b/src/core/widgets/webkit/webkit_ui.py @@ -44,6 +44,7 @@ class WebkitUI(WebKit2.WebView): data = f.read() self.load_html(content = data, base_uri = f"file://{path}/") + # self.load_uri("https://duckduckgo.com/") def _setup_content_manager(self): content_manager = self.get_user_content_manager() diff --git a/src/libs/controllers/controller_base.py b/src/libs/controllers/controller_base.py index fe2e5cd..88d564d 100644 --- a/src/libs/controllers/controller_base.py +++ b/src/libs/controllers/controller_base.py @@ -3,7 +3,7 @@ # Lib imports # Application imports -from libs.singleton import Singleton +from ..singleton import Singleton from ..dto.base_event import BaseEvent @@ -25,7 +25,7 @@ class ControllerBase(Singleton, EmitDispatcher): 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): self.controller_context = controller_context diff --git a/src/libs/controllers/controller_manager.py b/src/libs/controllers/controller_manager.py index e7d010f..1288b2b 100644 --- a/src/libs/controllers/controller_manager.py +++ b/src/libs/controllers/controller_manager.py @@ -24,7 +24,7 @@ class ControllerManager(Singleton, dict): def _crete_controller_context(self) -> ControllerContext: controller_context = ControllerContext() controller_context.message_to = self.message_to - controller_context.message = self.message + controller_context.message = self.message return controller_context diff --git a/src/libs/dto/code/__init__.py b/src/libs/dto/code/__init__.py index c3c48a4..b814883 100644 --- a/src/libs/dto/code/__init__.py +++ b/src/libs/dto/code/__init__.py @@ -4,6 +4,8 @@ from .code_event import CodeEvent +from .register_provider_event import RegisterProviderEvent + from .get_command_system_event import GetCommandSystemEvent from .request_completion_event import RequestCompletionEvent from .cursor_moved_event import CursorMovedEvent @@ -18,6 +20,7 @@ from .added_new_file_event import AddedNewFileEvent from .swapped_file_event import SwappedFileEvent from .popped_file_event import PoppedFileEvent from .removed_file_event import RemovedFileEvent +from .saved_file_event import SavedFileEvent from .get_file_event import GetFileEvent from .get_swap_file_event import GetSwapFileEvent diff --git a/src/libs/dto/code/register_provider_event.py b/src/libs/dto/code/register_provider_event.py new file mode 100644 index 0000000..b0a0b38 --- /dev/null +++ b/src/libs/dto/code/register_provider_event.py @@ -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: []) diff --git a/src/libs/dto/code/saved_file_event.py b/src/libs/dto/code/saved_file_event.py new file mode 100644 index 0000000..42636ec --- /dev/null +++ b/src/libs/dto/code/saved_file_event.py @@ -0,0 +1,13 @@ +# Python imports +from dataclasses import dataclass, field + +# Lib imports + +# Application imports +from .code_event import CodeEvent + + + +@dataclass +class SavedFileEvent(CodeEvent): + ... diff --git a/src/libs/dto/code/text_inserted_event.py b/src/libs/dto/code/text_inserted_event.py index 30f5f2e..8bdbe96 100644 --- a/src/libs/dto/code/text_inserted_event.py +++ b/src/libs/dto/code/text_inserted_event.py @@ -2,6 +2,11 @@ from dataclasses import dataclass, field # Lib imports +import gi + +gi.require_version('Gtk', '3.0') + +from gi.repository import Gtk # Application imports from .code_event import CodeEvent @@ -10,6 +15,6 @@ from .code_event import CodeEvent @dataclass class TextInsertedEvent(CodeEvent): - line: int = 0 - char: int = 0 - value: str = "" + location: Gtk.TextIter = None + text: str = "" + length: int = 0 diff --git a/src/libs/dto/states/__init__.py b/src/libs/dto/states/__init__.py new file mode 100644 index 0000000..1f05351 --- /dev/null +++ b/src/libs/dto/states/__init__.py @@ -0,0 +1,7 @@ +""" + Code DTO States Package +""" + +from .source_view_states import SourceViewStates +from .cursor_action import CursorAction +from .move_direction import MoveDirection diff --git a/src/libs/dto/states/cursor_action.py b/src/libs/dto/states/cursor_action.py new file mode 100644 index 0000000..810561d --- /dev/null +++ b/src/libs/dto/states/cursor_action.py @@ -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 diff --git a/src/libs/dto/states/move_direction.py b/src/libs/dto/states/move_direction.py new file mode 100644 index 0000000..9ef314a --- /dev/null +++ b/src/libs/dto/states/move_direction.py @@ -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 diff --git a/src/libs/dto/states/source_view_states.py b/src/libs/dto/states/source_view_states.py new file mode 100644 index 0000000..b1c3c69 --- /dev/null +++ b/src/libs/dto/states/source_view_states.py @@ -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 diff --git a/src/libs/settings/other/webkit_ui_settings.py b/src/libs/settings/other/webkit_ui_settings.py index 981ea49..955860c 100644 --- a/src/libs/settings/other/webkit_ui_settings.py +++ b/src/libs/settings/other/webkit_ui_settings.py @@ -39,4 +39,5 @@ class WebkitUISettings(WebKit2.Settings): self.set_enable_webaudio(True) self.set_enable_accelerated_2d_canvas(True) - self.set_user_agent(f"{APP_NAME}") \ No newline at end of file + self.set_user_agent(f"Mozilla/5.0 (macOS, AArch64) {APP_NAME}/1.0 Chrome/140.0.0 AppleWebKit/537.36 Safari/537.36") + diff --git a/src/plugins/controller.py b/src/plugins/controller.py index 588798c..ead0ebf 100644 --- a/src/plugins/controller.py +++ b/src/plugins/controller.py @@ -55,7 +55,7 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi for file in os.listdir(path): _path = os.path.join(path, file) if os.path.isdir(_path): - self.collect_search_locations(_path, locations) + self._collect_search_locations(_path, locations) def _load_plugins( self, @@ -70,7 +70,7 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi try: target = join(path, "plugin.py") 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) diff --git a/src/plugins/manifest_manager.py b/src/plugins/manifest_manager.py index f065c0f..e41b92d 100644 --- a/src/plugins/manifest_manager.py +++ b/src/plugins/manifest_manager.py @@ -32,11 +32,8 @@ class ManifestManager: for path, folder in [ [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) + if os.path.isdir( join(self._plugins_path, item) ) ]: self.load(folder, path) diff --git a/src/plugins/plugin_types/__init__.py b/src/plugins/plugin_types/__init__.py new file mode 100644 index 0000000..5814687 --- /dev/null +++ b/src/plugins/plugin_types/__init__.py @@ -0,0 +1,7 @@ +""" + Plugin Types Module +""" + +from .plugin_base import PluginBase +from .plugin_ui import PluginUI +from .plugin_code import PluginCode diff --git a/src/plugins/plugin_types/plugin_base.py b/src/plugins/plugin_types/plugin_base.py new file mode 100644 index 0000000..1b1b97a --- /dev/null +++ b/src/plugins/plugin_types/plugin_base.py @@ -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") diff --git a/src/plugins/plugin_types/plugin_code.py b/src/plugins/plugin_types/plugin_code.py new file mode 100644 index 0000000..37438c1 --- /dev/null +++ b/src/plugins/plugin_types/plugin_code.py @@ -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) diff --git a/src/plugins/plugin_base.py b/src/plugins/plugin_types/plugin_ui.py similarity index 68% rename from src/plugins/plugin_base.py rename to src/plugins/plugin_types/plugin_ui.py index 08a1ccc..f5e3534 100644 --- a/src/plugins/plugin_base.py +++ b/src/plugins/plugin_types/plugin_ui.py @@ -5,30 +5,31 @@ # Application imports 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): - super(PluginBase, self).__init__(*args, **kwargs) + super(PluginUI, 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") + raise PluginCodeException("Plugin UI '_controller_message' must be overriden by Plugin") 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): - 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): return self.plugin_context.requests_ui_element(element_id) diff --git a/src/plugins/plugins_controller_mixin.py b/src/plugins/plugins_controller_mixin.py index 0691525..3583ee3 100644 --- a/src/plugins/plugins_controller_mixin.py +++ b/src/plugins/plugins_controller_mixin.py @@ -6,6 +6,10 @@ +class InvalidPluginException(Exception): + ... + + class PluginsControllerMixin: diff --git a/user_config/usr/share/app_name/stylesheet.css b/user_config/usr/share/app_name/stylesheet.css index d8d272c..fef2246 100644 --- a/user_config/usr/share/app_name/stylesheet.css +++ b/user_config/usr/share/app_name/stylesheet.css @@ -1,21 +1,56 @@ -/* ---- Make most desired things base transparent ---- */ +/* ---- Overrides ---- */ +text selection { + background-color: #44475a; + color: #FFA500; +} /* ---- Make most desired things base transparent ---- */ popover, popover > box, notebook, header, -stack, -scrolledwindow, -viewport { +stack { background: rgba(0, 0, 0, 0.0); 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 { background: rgba(39, 43, 52, 0.84); } @@ -41,6 +76,20 @@ tab:checked { 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 { margin: 10px; } @@ -96,177 +145,9 @@ tab:checked { color: rgba(255, 0, 0, 0.64); } - - - - - - - - - -/*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); } +.error-txt { color: rgb(170, 18, 18); } +.warning-txt { color: rgb(255, 168, 0); } +.success-txt { color: rgb(136, 204, 39); }