initial commit
This commit is contained in:
79
src/__builtins__.py
Normal file
79
src/__builtins__.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# Python imports
|
||||
import builtins
|
||||
import traceback
|
||||
import threading
|
||||
import sys
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
# from libs.db import DB
|
||||
from libs.event_system import EventSystem
|
||||
from libs.endpoint_registry import EndpointRegistry
|
||||
from libs.keybindings import Keybindings
|
||||
from libs.logger import Logger
|
||||
from libs.settings.manager import SettingsManager
|
||||
|
||||
|
||||
|
||||
# NOTE: Threads WILL NOT die with parent's destruction.
|
||||
def threaded_wrapper(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
thread = threading.Thread(target = fn, args = args, kwargs = kwargs, daemon = False)
|
||||
thread.start()
|
||||
return thread
|
||||
return wrapper
|
||||
|
||||
# NOTE: Threads WILL die with parent's destruction.
|
||||
def daemon_threaded_wrapper(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
thread = threading.Thread(target = fn, args = args, kwargs = kwargs, daemon = True)
|
||||
thread.start()
|
||||
return thread
|
||||
return wrapper
|
||||
|
||||
def call_chain_wrapper(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
print()
|
||||
print()
|
||||
for line in traceback.format_stack():
|
||||
print( line.strip() )
|
||||
print()
|
||||
print()
|
||||
|
||||
return fn(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
# NOTE: Just reminding myself we can add to builtins two different ways...
|
||||
# __builtins__.update({"event_system": Builtins()})
|
||||
builtins.APP_NAME = "Newton"
|
||||
|
||||
builtins.keybindings = Keybindings()
|
||||
builtins.event_system = EventSystem()
|
||||
builtins.endpoint_registry = EndpointRegistry()
|
||||
builtins.settings_manager = SettingsManager()
|
||||
# builtins.db = DB()
|
||||
|
||||
settings_manager.load_settings()
|
||||
|
||||
builtins.logger = Logger(
|
||||
settings_manager.path_manager.get_home_config_path(), \
|
||||
_ch_log_lvl = settings_manager.settings.debugging.ch_log_lvl, \
|
||||
_fh_log_lvl = settings_manager.settings.debugging.fh_log_lvl
|
||||
).get_logger()
|
||||
|
||||
builtins.threaded = threaded_wrapper
|
||||
builtins.daemon_threaded = daemon_threaded_wrapper
|
||||
builtins.call_chain = call_chain_wrapper
|
||||
|
||||
|
||||
|
||||
# def custom_except_hook(exc_type, exc_value, exc_traceback):
|
||||
# if issubclass(exc_type, KeyboardInterrupt):
|
||||
# sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||
# return
|
||||
|
||||
# logger.error("Uncaught exception", exc_info = (exc_type, exc_value, exc_traceback))
|
||||
|
||||
# sys.excepthook = custom_except_hook
|
||||
3
src/__init__.py
Normal file
3
src/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Src Package.
|
||||
"""
|
||||
58
src/__main__.py
Normal file
58
src/__main__.py
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
# Python imports
|
||||
import argparse
|
||||
import faulthandler
|
||||
import traceback
|
||||
from setproctitle import setproctitle
|
||||
|
||||
import tracemalloc
|
||||
tracemalloc.start()
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from __builtins__ import *
|
||||
from app import Application
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
setproctitle(f'{APP_NAME}')
|
||||
settings_manager.set_start_load_time()
|
||||
|
||||
if args.debug == "true":
|
||||
settings_manager.set_debug(True)
|
||||
|
||||
if args.trace_debug == "true":
|
||||
settings_manager.set_trace_debug(True)
|
||||
|
||||
settings_manager.do_dirty_start_check()
|
||||
|
||||
app = Application()
|
||||
app.run()
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
''' Set process title, get arguments, and create GTK main thread. '''
|
||||
|
||||
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", "-nt", default = "false", help = "Opens a 'New Tab' if a handler is set for it.")
|
||||
parser.add_argument("--file", "-f", default = "default", help = "JUST SOME FILE ARG.")
|
||||
|
||||
# Read arguments (If any...)
|
||||
args, unknownargs = parser.parse_known_args()
|
||||
settings_manager.set_starting_args( args, unknownargs )
|
||||
|
||||
try:
|
||||
faulthandler.enable() # For better debug info
|
||||
main()
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
quit()
|
||||
72
src/app.py
Normal file
72
src/app.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Python imports
|
||||
from contextlib import suppress
|
||||
import signal
|
||||
import os
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from libs.debugging import debug_signal_handler
|
||||
from libs.ipc_server import IPCServer
|
||||
from core.window import Window
|
||||
|
||||
|
||||
|
||||
class AppLaunchException(Exception):
|
||||
...
|
||||
|
||||
|
||||
|
||||
class Application:
|
||||
""" docstring for Application. """
|
||||
|
||||
def __init__(self):
|
||||
super(Application, self).__init__()
|
||||
|
||||
self.setup_debug_hook()
|
||||
|
||||
|
||||
def run(self):
|
||||
if not settings_manager.is_trace_debug():
|
||||
if not self.load_ipc():
|
||||
return
|
||||
|
||||
win = Window()
|
||||
win.start()
|
||||
|
||||
def load_ipc(self):
|
||||
args, \
|
||||
unknownargs = settings_manager.get_starting_args()
|
||||
ipc_server = IPCServer()
|
||||
|
||||
self.ipc_realization_check(ipc_server)
|
||||
if ipc_server.is_ipc_alive:
|
||||
return True
|
||||
|
||||
logger.warning(f"{app_name} IPC Server Exists: Have sent path(s) to it and closing...")
|
||||
for arg in unknownargs + [args.new_tab,]:
|
||||
if os.path.isfile(arg):
|
||||
message = f"FILE|{arg}"
|
||||
ipc_server.send_ipc_message(message)
|
||||
|
||||
if os.path.isdir(arg):
|
||||
message = f"DIR|{arg}"
|
||||
ipc_server.send_ipc_message(message)
|
||||
|
||||
return False
|
||||
|
||||
def ipc_realization_check(self, ipc_server):
|
||||
try:
|
||||
ipc_server.create_ipc_listener()
|
||||
except Exception:
|
||||
ipc_server.send_test_ipc_message()
|
||||
|
||||
def setup_debug_hook(self):
|
||||
# Typically: ValueError: signal only works in main thread
|
||||
with suppress(ValueError):
|
||||
# kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows
|
||||
signal.signal(
|
||||
vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR2"),
|
||||
debug_signal_handler
|
||||
)
|
||||
|
||||
3
src/core/__init__.py
Normal file
3
src/core/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Core Package
|
||||
"""
|
||||
33
src/core/builder_wrapper.py
Normal file
33
src/core/builder_wrapper.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class BuilderWrapper(Gtk.Builder):
|
||||
"""docstring for BuilderWrapper."""
|
||||
|
||||
def __init__(self):
|
||||
super(BuilderWrapper, self).__init__()
|
||||
|
||||
self.objects = {}
|
||||
|
||||
def get_object(self, id: str, use_gtk: bool = True) -> any:
|
||||
if not use_gtk:
|
||||
return self.objects[id]
|
||||
|
||||
return super(BuilderWrapper, self).get_object(id)
|
||||
|
||||
def expose_object(self, id: str, object: any, use_gtk: bool = True) -> None:
|
||||
if not use_gtk:
|
||||
self.objects[id] = object
|
||||
else:
|
||||
super(BuilderWrapper, self).expose_object(id, object)
|
||||
|
||||
def dereference_object(self, id: str) -> None:
|
||||
del self.objects[id]
|
||||
49
src/core/containers/base_container.py
Normal file
49
src/core/containers/base_container.py
Normal file
@@ -0,0 +1,49 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from .header_container import HeaderContainer
|
||||
from .body_container import BodyContainer
|
||||
from .footer_container import FooterContainer
|
||||
|
||||
|
||||
|
||||
class BaseContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(BaseContainer, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("base-container")
|
||||
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("update-transparency", self._update_transparency)
|
||||
event_system.subscribe("remove-transparency", self._remove_transparency)
|
||||
|
||||
def _load_widgets(self):
|
||||
self.add( HeaderContainer() )
|
||||
self.add( BodyContainer() )
|
||||
self.add( FooterContainer() )
|
||||
|
||||
def _update_transparency(self):
|
||||
self.ctx.add_class(f"mw_transparency_{settings_manager.settings.theming.transparency}")
|
||||
|
||||
def _remove_transparency(self):
|
||||
self.ctx.remove_class(f"mw_transparency_{settings_manager.settings.theming.transparency}")
|
||||
42
src/core/containers/body_container.py
Normal file
42
src/core/containers/body_container.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from .left_container import LeftContainer
|
||||
from .center_container import CenterContainer
|
||||
from .right_container import RightContainer
|
||||
|
||||
|
||||
|
||||
class BodyContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(BodyContainer, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("body-container")
|
||||
|
||||
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
self.add( LeftContainer() )
|
||||
self.add( CenterContainer() )
|
||||
self.add( RightContainer() )
|
||||
48
src/core/containers/center_container.py
Normal file
48
src/core/containers/center_container.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.code.general_info_widget import GeneralInfoWidget
|
||||
from .code.code_container import CodeContainer
|
||||
from ..widgets.save_file_dialog import SaveFileDialog
|
||||
from ..widgets.controls.open_files_button import OpenFilesButton
|
||||
|
||||
|
||||
|
||||
class CenterContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(CenterContainer, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("center-container")
|
||||
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.set_hexpand(True)
|
||||
self.set_vexpand(True)
|
||||
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
SaveFileDialog()
|
||||
OpenFilesButton()
|
||||
|
||||
self.add( GeneralInfoWidget() )
|
||||
self.add( CodeContainer() )
|
||||
3
src/core/containers/code/__init__.py
Normal file
3
src/core/containers/code/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Containers Package
|
||||
"""
|
||||
38
src/core/containers/code/code_container.py
Normal file
38
src/core/containers/code/code_container.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ...widgets.code.tabs_widget import TabsWidget
|
||||
|
||||
from .editors_container import EditorsContainer
|
||||
|
||||
|
||||
|
||||
class CodeContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(CodeContainer, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
self.add( TabsWidget() )
|
||||
self.add( EditorsContainer() )
|
||||
38
src/core/containers/code/editors_container.py
Normal file
38
src/core/containers/code/editors_container.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ...widgets.separator_widget import Separator
|
||||
from ...widgets.code.miniview_widget import MiniViewWidget
|
||||
from .paned_editors_container import PanedEditorsContainer
|
||||
|
||||
|
||||
|
||||
class EditorsContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(EditorsContainer, 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):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
self.add( Separator("separator_left") )
|
||||
self.add( PanedEditorsContainer() )
|
||||
self.add( Separator("separator_right") )
|
||||
self.add( MiniViewWidget() )
|
||||
69
src/core/containers/code/paned_editors_container.py
Normal file
69
src/core/containers/code/paned_editors_container.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from ...widgets.code.view import SourceView
|
||||
|
||||
|
||||
|
||||
class PanedEditorsContainer(Gtk.Paned):
|
||||
def __init__(self):
|
||||
super(PanedEditorsContainer, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("paned-editors-container")
|
||||
|
||||
self.set_hexpand(True)
|
||||
self.set_vexpand(True)
|
||||
# self.set_homogeneous(True)
|
||||
self.set_wide_handle(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.map_id = self.connect("map", self._init_map)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
scrolled_win1 = Gtk.ScrolledWindow()
|
||||
scrolled_win2 = Gtk.ScrolledWindow()
|
||||
source_view1 = SourceView()
|
||||
source_view2 = SourceView()
|
||||
|
||||
source_view1.sibling_right = source_view2
|
||||
source_view2.sibling_left = source_view1
|
||||
|
||||
scrolled_win1.add( source_view1 )
|
||||
scrolled_win2.add( source_view2 )
|
||||
|
||||
self.add1(scrolled_win1)
|
||||
self.add2(scrolled_win2)
|
||||
|
||||
def _init_map(self, view):
|
||||
def _first_show_init():
|
||||
self.disconnect(self.map_id)
|
||||
del self.map_id
|
||||
|
||||
self._handle_first_show()
|
||||
|
||||
return False
|
||||
|
||||
GLib.timeout_add(250, _first_show_init)
|
||||
|
||||
def _handle_first_show(self):
|
||||
self.set_position(
|
||||
self.get_allocated_width() / 2
|
||||
)
|
||||
|
||||
43
src/core/containers/footer_container.py
Normal file
43
src/core/containers/footer_container.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.vte_widget import VteWidget
|
||||
|
||||
|
||||
|
||||
class FooterContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(FooterContainer, self).__init__()
|
||||
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("footer-container")
|
||||
|
||||
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||
self.set_hexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
vte_widget = VteWidget()
|
||||
|
||||
vte_widget.hide()
|
||||
self.add( vte_widget )
|
||||
40
src/core/containers/header_container.py
Normal file
40
src/core/containers/header_container.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.controls.transparency_scale import TransparencyScale
|
||||
|
||||
|
||||
|
||||
class HeaderContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(HeaderContainer, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("header-container")
|
||||
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.set_hexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("tggl-top-main-menubar", self.tggl_top_main_menubar)
|
||||
|
||||
def _load_widgets(self):
|
||||
self.add( TransparencyScale() )
|
||||
|
||||
def tggl_top_main_menubar(self):
|
||||
self.hide() if self.is_visible() else self.show_all()
|
||||
39
src/core/containers/left_container.py
Normal file
39
src/core/containers/left_container.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ..widgets.code.miniview_widget import MiniViewWidget
|
||||
|
||||
|
||||
|
||||
class LeftContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(LeftContainer, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("left-container")
|
||||
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.set_vexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
38
src/core/containers/right_container.py
Normal file
38
src/core/containers/right_container.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class RightContainer(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(RightContainer, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.ctx = self.get_style_context()
|
||||
self.ctx.add_class("right-container")
|
||||
|
||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||
self.set_vexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
3
src/core/controllers/__init__.py
Normal file
3
src/core/controllers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Controllers Package
|
||||
"""
|
||||
71
src/core/controllers/base_controller.py
Normal file
71
src/core/controllers/base_controller.py
Normal file
@@ -0,0 +1,71 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from libs.mixins.ipc_signals_mixin import IPCSignalsMixin
|
||||
from libs.mixins.keyboard_signals_mixin import KeyboardSignalsMixin
|
||||
|
||||
from ..containers.base_container import BaseContainer
|
||||
|
||||
from .base_controller_data import BaseControllerData
|
||||
from .bridge_controller import BridgeController
|
||||
|
||||
|
||||
|
||||
class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
|
||||
""" docstring for BaseController. """
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self._setup_controller_data()
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_controllers()
|
||||
self._load_plugins_and_files()
|
||||
|
||||
logger.info(f"Made it past {self.__class__} loading...")
|
||||
settings_manager.set_end_load_time()
|
||||
settings_manager.log_load_time()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
# self.window.connect("focus-out-event", self.unset_keys_and_data)
|
||||
# self.window.connect("key-press-event", self.on_global_key_press_controller)
|
||||
# self.window.connect("key-release-event", self.on_global_key_release_controller)
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("shutting-down", lambda: print("Shutting down..."))
|
||||
event_system.subscribe("handle-file-from-ipc", self.handle_file_from_ipc)
|
||||
event_system.subscribe("handle-dir-from-ipc", self.handle_dir_from_ipc)
|
||||
event_system.subscribe("tggl-top-main-menubar", self._tggl_top_main_menubar)
|
||||
|
||||
def _load_controllers(self):
|
||||
BridgeController()
|
||||
|
||||
def _load_plugins_and_files(self):
|
||||
args, unknownargs = settings_manager.get_starting_args()
|
||||
|
||||
if args.no_plugins == "false":
|
||||
self.plugins_controller.pre_launch_plugins()
|
||||
self.plugins_controller.post_launch_plugins()
|
||||
|
||||
def _tggl_top_main_menubar(self):
|
||||
logger.debug("_tggl_top_main_menubar > stub...")
|
||||
|
||||
def _load_glade_file(self):
|
||||
self.builder.add_from_file( settings_manager.path_manager.get_glade_file() )
|
||||
self.builder.expose_object("main_window", self.window)
|
||||
|
||||
settings_manager.set_builder(self.builder)
|
||||
self.base_container = BaseContainer()
|
||||
|
||||
settings_manager.register_signals_to_builder([self, self.base_container])
|
||||
107
src/core/controllers/base_controller_data.py
Normal file
107
src/core/controllers/base_controller_data.py
Normal file
@@ -0,0 +1,107 @@
|
||||
# Python imports
|
||||
import os
|
||||
import subprocess
|
||||
from shutil import which
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from plugins.plugins_controller import PluginsController
|
||||
from ..builder_wrapper import BuilderWrapper
|
||||
|
||||
|
||||
|
||||
class BaseControllerData:
|
||||
''' BaseControllerData contains most of the state of the app at ay given time. It also has some support methods. '''
|
||||
|
||||
def _setup_controller_data(self) -> None:
|
||||
self.window = settings_manager.get_main_window()
|
||||
self.builder = BuilderWrapper()
|
||||
self.plugins_controller = PluginsController()
|
||||
|
||||
self.base_container = None
|
||||
self.was_midified_key = False
|
||||
self.ctrl_down = False
|
||||
self.shift_down = False
|
||||
self.alt_down = False
|
||||
|
||||
self._collect_files_dirs()
|
||||
self._load_glade_file()
|
||||
|
||||
|
||||
def _collect_files_dirs(self):
|
||||
args, \
|
||||
unknownargs = settings_manager.get_starting_args()
|
||||
files = []
|
||||
|
||||
for arg in unknownargs + [args.new_tab,]:
|
||||
if os.path.isdir( arg.replace("file://", "") ):
|
||||
files.append( f"DIR|{arg.replace('file://', '')}" )
|
||||
continue
|
||||
|
||||
# NOTE: If passing line number with file split against :
|
||||
if os.path.isfile( arg.replace("file://", "").split(":")[0] ):
|
||||
files.append( f"FILE|{arg.replace('file://', '')}" )
|
||||
continue
|
||||
|
||||
logger.info(f"Not a File: {arg}")
|
||||
|
||||
if len(files) == 0: return
|
||||
|
||||
settings_manager.set_is_starting_with_file(True)
|
||||
settings_manager.set_starting_files(files)
|
||||
|
||||
def get_base_container(self):
|
||||
return self.base_container
|
||||
|
||||
def clear_console(self) -> None:
|
||||
''' Clears the terminal screen. '''
|
||||
os.system('cls' if os.name == 'nt' else 'clear')
|
||||
|
||||
def call_method(self, _method_name: str, data: type) -> type:
|
||||
'''
|
||||
Calls a method from scope of class.
|
||||
|
||||
Parameters:
|
||||
a (obj): self
|
||||
b (str): method name to be called
|
||||
c (*): Data (if any) to be passed to the method.
|
||||
Note: It must be structured according to the given methods requirements.
|
||||
|
||||
Returns:
|
||||
Return data is that which the calling method gives.
|
||||
'''
|
||||
method_name = str(_method_name)
|
||||
method = getattr(self, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}")
|
||||
return method(*data) if data else method()
|
||||
|
||||
def has_method(self, obj: type, method: type) -> type:
|
||||
''' Checks if a given method exists. '''
|
||||
return callable(getattr(obj, method, None))
|
||||
|
||||
def clear_children(self, widget: type) -> None:
|
||||
''' Clear children of a gtk widget. '''
|
||||
for child in widget.get_children():
|
||||
widget.remove(child)
|
||||
|
||||
def get_clipboard_data(self, encoding = "utf-8") -> str:
|
||||
if not which("xclip"):
|
||||
logger.info('xclip not found...')
|
||||
return
|
||||
|
||||
command = ['xclip','-selection','clipboard']
|
||||
proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout = subprocess.PIPE)
|
||||
retcode = proc.wait()
|
||||
data = proc.stdout.read()
|
||||
return data.decode(encoding).strip()
|
||||
|
||||
def set_clipboard_data(self, data: type, encoding = "utf-8") -> None:
|
||||
if not which("xclip"):
|
||||
logger.info('xclip not found...')
|
||||
return
|
||||
|
||||
command = ['xclip','-selection','clipboard']
|
||||
proc = subprocess.Popen(command, stdin = subprocess.PIPE)
|
||||
proc.stdin.write(data.encode(encoding))
|
||||
proc.stdin.close()
|
||||
retcode = proc.wait()
|
||||
41
src/core/controllers/bridge_controller.py
Normal file
41
src/core/controllers/bridge_controller.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# Python imports
|
||||
import base64
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class BridgeController:
|
||||
def __init__(self):
|
||||
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("handle-bridge-event", self.handle_bridge_event)
|
||||
|
||||
|
||||
def handle_bridge_event(self, event):
|
||||
match event.topic:
|
||||
case "save":
|
||||
event_system.emit(f"handle-file-event-{event.originator}", (event,))
|
||||
case "close":
|
||||
event_system.emit(f"handle-file-event-{event.originator}", (event,))
|
||||
case "load_buffer":
|
||||
event_system.emit(f"handle-file-event-{event.originator}", (event,))
|
||||
case "load_file":
|
||||
event_system.emit(f"handle-file-event-{event.originator}", (event,))
|
||||
case "alert":
|
||||
content = base64.b64decode( event.content.encode() ).decode("utf-8")
|
||||
logger.info(f"\nMessage Topic: {event.topic}\nMessage Content: {content}")
|
||||
case "error":
|
||||
content = base64.b64decode( event.content.encode() ).decode("utf-8")
|
||||
logger.info(content)
|
||||
case _:
|
||||
...
|
||||
3
src/core/widgets/__init__.py
Normal file
3
src/core/widgets/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Widgets Package
|
||||
"""
|
||||
3
src/core/widgets/code/__init__.py
Normal file
3
src/core/widgets/code/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Code Package
|
||||
"""
|
||||
34
src/core/widgets/code/command_system.py
Normal file
34
src/core/widgets/code/command_system.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from . import commands
|
||||
|
||||
|
||||
|
||||
class CommandSystem:
|
||||
def __init__(self):
|
||||
super(CommandSystem, self).__init__()
|
||||
|
||||
self.data: list = ()
|
||||
|
||||
|
||||
def set_data(self, *args, **kwargs):
|
||||
self.data = (args, kwargs)
|
||||
|
||||
def exec(self, command: str) -> any:
|
||||
if not hasattr(commands, command): return
|
||||
method = getattr(commands, command)
|
||||
|
||||
args, kwargs = self.data
|
||||
if kwargs:
|
||||
return method.execute(*args, kwargs)
|
||||
else:
|
||||
return method.execute(*args)
|
||||
|
||||
def exec_with_args(self, command: str, args: list) -> any:
|
||||
if not hasattr(commands, command): return
|
||||
|
||||
method = getattr(commands, command)
|
||||
return method.execute(*args)
|
||||
16
src/core/widgets/code/commands/__init__.py
Normal file
16
src/core/widgets/code/commands/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Commands Package
|
||||
"""
|
||||
|
||||
import pkgutil
|
||||
import importlib
|
||||
|
||||
__all__ = []
|
||||
|
||||
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
|
||||
module = importlib.import_module(f"{__name__}.{module_name}")
|
||||
globals()[module_name] = module # Add module to package namespace
|
||||
__all__.append(module_name)
|
||||
|
||||
del pkgutil
|
||||
del importlib
|
||||
23
src/core/widgets/code/commands/buffer_redo.py
Normal file
23
src/core/widgets/code/commands/buffer_redo.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Buffer Redo Command")
|
||||
|
||||
buffer = view.get_buffer()
|
||||
undo_manager = buffer.get_undo_manager()
|
||||
|
||||
if undo_manager.can_redo():
|
||||
buffer.redo()
|
||||
23
src/core/widgets/code/commands/buffer_undo.py
Normal file
23
src/core/widgets/code/commands/buffer_undo.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Buffer Undo Command")
|
||||
|
||||
buffer = view.get_buffer()
|
||||
undo_manager = buffer.get_undo_manager()
|
||||
|
||||
if undo_manager.can_undo():
|
||||
buffer.undo()
|
||||
26
src/core/widgets/code/commands/close_file.py
Normal file
26
src/core/widgets/code/commands/close_file.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Close File Command")
|
||||
buffer = view.get_buffer()
|
||||
|
||||
next_file = view.files_manager.remove_file(buffer)
|
||||
if not next_file:
|
||||
view.command.exec("new_file")
|
||||
else:
|
||||
view.set_buffer(next_file.buffer)
|
||||
|
||||
view.command.exec("update_info_bar")
|
||||
36
src/core/widgets/code/commands/cut_to_temp_buffer.py
Normal file
36
src/core/widgets/code/commands/cut_to_temp_buffer.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Cut to Temp Buffer Command")
|
||||
|
||||
view.clear_temp_cut_buffer_delayed()
|
||||
|
||||
buffer = view.get_buffer()
|
||||
itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||
start_itr = itr.copy()
|
||||
end_itr = itr.copy()
|
||||
start_line = itr.get_line() + 1
|
||||
start_char = itr.get_line_offset()
|
||||
|
||||
start_itr.backward_visible_line()
|
||||
start_itr.forward_line()
|
||||
end_itr.forward_line()
|
||||
|
||||
line_str = buffer.get_slice(start_itr, end_itr, True)
|
||||
view._cut_buffer += f"{line_str}"
|
||||
buffer.delete(start_itr, end_itr)
|
||||
|
||||
view.set_temp_cut_buffer_delayed()
|
||||
35
src/core/widgets/code/commands/dnd_load_file_to_buffer.py
Normal file
35
src/core/widgets/code/commands/dnd_load_file_to_buffer.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
uri: str
|
||||
):
|
||||
logger.debug("DnD Load File To Buffer Command")
|
||||
|
||||
buffer = view.get_buffer()
|
||||
file = view.files_manager.get_file(buffer)
|
||||
|
||||
if not file.ftype == "buffer":
|
||||
file = view.command.exec("new_file")
|
||||
|
||||
gfile = Gio.File.new_for_uri(uri)
|
||||
view.command.exec_with_args(
|
||||
"load_file",
|
||||
(view, gfile, file)
|
||||
)
|
||||
|
||||
has_focus = view.command.exec("has_focus")
|
||||
if has_focus:
|
||||
view.command.exec("update_info_bar")
|
||||
27
src/core/widgets/code/commands/dnd_load_files.py
Normal file
27
src/core/widgets/code/commands/dnd_load_files.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
from ..source_file import SourceFile
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
uris: list = []
|
||||
):
|
||||
logger.debug("DnD Load Files Command")
|
||||
for uri in uris:
|
||||
try:
|
||||
gfile = Gio.File.new_for_uri(uri)
|
||||
except Exception as e:
|
||||
gfile = Gio.File.new_for_path(uri)
|
||||
|
||||
view.command.exec_with_args("load_file", (view, gfile))
|
||||
53
src/core/widgets/code/commands/duplicate_line.py
Normal file
53
src/core/widgets/code/commands/duplicate_line.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Duplicate Line Command")
|
||||
|
||||
buffer = view.get_buffer()
|
||||
|
||||
if not buffer.get_has_selection():
|
||||
had_selection = False
|
||||
itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||
start_itr = itr.copy()
|
||||
end_itr = itr.copy()
|
||||
start_line = itr.get_line() + 1
|
||||
start_char = itr.get_line_offset()
|
||||
else:
|
||||
had_selection = True
|
||||
start_itr, end_itr = buffer.get_selection_bounds()
|
||||
sline = start_itr.get_line()
|
||||
eline = end_itr.get_line()
|
||||
start_line = eline + 1
|
||||
start_char = start_itr.get_line_offset()
|
||||
end_char = end_itr.get_line_offset()
|
||||
range_line_size = eline - sline
|
||||
|
||||
start_itr.backward_visible_line()
|
||||
start_itr.forward_line()
|
||||
end_itr.forward_line()
|
||||
end_itr.backward_char()
|
||||
|
||||
line_str = buffer.get_slice(start_itr, end_itr, True)
|
||||
end_itr.forward_char()
|
||||
buffer.insert(end_itr, f"{line_str}\n", -1)
|
||||
|
||||
if not had_selection:
|
||||
new_itr = buffer.get_iter_at_line_offset(start_line, start_char)
|
||||
buffer.place_cursor(new_itr)
|
||||
else:
|
||||
new_itr = buffer.get_iter_at_line_offset(start_line, start_char)
|
||||
new_end_itr = buffer.get_iter_at_line_offset((start_line + range_line_size), end_char)
|
||||
buffer.select_range(new_itr, new_end_itr)
|
||||
19
src/core/widgets/code/commands/focus_left_sibling.py
Normal file
19
src/core/widgets/code/commands/focus_left_sibling.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Focus Left Sibling Command")
|
||||
if not view.sibling_left: return
|
||||
view.sibling_left.grab_focus()
|
||||
19
src/core/widgets/code/commands/focus_right_sibling.py
Normal file
19
src/core/widgets/code/commands/focus_right_sibling.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Focus Right Sibling Command")
|
||||
if not view.sibling_right: return
|
||||
view.sibling_right.grab_focus()
|
||||
20
src/core/widgets/code/commands/get_current_file.py
Normal file
20
src/core/widgets/code/commands/get_current_file.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Get Current File Command")
|
||||
|
||||
buffer = view.get_buffer()
|
||||
return view.files_manager.get_file(buffer)
|
||||
21
src/core/widgets/code/commands/get_filetype.py
Normal file
21
src/core/widgets/code/commands/get_filetype.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Get File Type Command")
|
||||
|
||||
buffer = view.get_buffer()
|
||||
file = view.files_manager.get_file(buffer)
|
||||
return file.ftype
|
||||
21
src/core/widgets/code/commands/get_text.py
Normal file
21
src/core/widgets/code/commands/get_text.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Get Text Command")
|
||||
|
||||
buffer = view.get_buffer()
|
||||
start_itr, end_itr = buffer.get_bounds()
|
||||
return buffer.get_text(start_itr, end_itr, True)
|
||||
31
src/core/widgets/code/commands/go_to.py
Normal file
31
src/core/widgets/code/commands/go_to.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Go-To Command")
|
||||
|
||||
file = view.command.exec("get_current_file")
|
||||
gfile = file.get_location()
|
||||
uri = gfile.get_uri()
|
||||
|
||||
buffer = view.get_buffer()
|
||||
iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||
line = iter.get_line()
|
||||
offset = iter.get_line_offset()
|
||||
|
||||
event_system.emit(
|
||||
"textDocument/definition",
|
||||
(view, file.ftype, uri, line, offset,)
|
||||
)
|
||||
19
src/core/widgets/code/commands/has_focus.py
Normal file
19
src/core/widgets/code/commands/has_focus.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Has Focus Command")
|
||||
ctx = view.get_parent().get_style_context()
|
||||
return ctx.has_class("source-view-focused")
|
||||
18
src/core/widgets/code/commands/line_down.py
Normal file
18
src/core/widgets/code/commands/line_down.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Line Up Command")
|
||||
view.emit("move-lines", True)
|
||||
18
src/core/widgets/code/commands/line_up.py
Normal file
18
src/core/widgets/code/commands/line_up.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Line Up Command")
|
||||
view.emit("move-lines", False)
|
||||
32
src/core/widgets/code/commands/load_file.py
Normal file
32
src/core/widgets/code/commands/load_file.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
from ..source_file import SourceFile
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
gfile: Gio.File,
|
||||
file: SourceFile = None,
|
||||
):
|
||||
logger.debug("Load File Command")
|
||||
if not file:
|
||||
file = view.files_manager.new()
|
||||
|
||||
file.load_path(gfile)
|
||||
|
||||
language = view.language_manager \
|
||||
.guess_language(file.fname, None)
|
||||
file.ftype = language
|
||||
|
||||
file.buffer.set_language(language)
|
||||
file.buffer.set_style_scheme(view.syntax_theme)
|
||||
42
src/core/widgets/code/commands/load_start_files.py
Normal file
42
src/core/widgets/code/commands/load_start_files.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
from ..source_file import SourceFile
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
):
|
||||
logger.debug("Load Start File(s) Command")
|
||||
|
||||
starting_files = settings_manager.get_starting_files()
|
||||
|
||||
if len(starting_files) == 0: return
|
||||
|
||||
file = starting_files.pop()
|
||||
file = file.replace("FILE|", "")
|
||||
gfile = Gio.File.new_for_path(file)
|
||||
buffer = view.get_buffer()
|
||||
file = view.files_manager.get_file(buffer)
|
||||
|
||||
view.command.exec_with_args(
|
||||
"load_file",
|
||||
(view, gfile, file)
|
||||
)
|
||||
|
||||
if len(starting_files) == 0: return
|
||||
|
||||
for file in starting_files:
|
||||
file = file.replace("FILE|", "")
|
||||
gfile = Gio.File.new_for_path(file)
|
||||
|
||||
view.command.exec_with_args("load_file", (view, gfile))
|
||||
32
src/core/widgets/code/commands/move_to_left_sibling.py
Normal file
32
src/core/widgets/code/commands/move_to_left_sibling.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Move To Left Sibling Command")
|
||||
if not view.sibling_left: return
|
||||
|
||||
buffer = view.get_buffer()
|
||||
popped_file, next_file = view.files_manager.swap_file(buffer)
|
||||
|
||||
popped_file.remove_observer(view)
|
||||
popped_file.add_observer(view.sibling_left)
|
||||
view.sibling_left.set_buffer(buffer)
|
||||
view.sibling_left.grab_focus()
|
||||
|
||||
if next_file:
|
||||
next_file.add_observer(view)
|
||||
view.set_buffer(next_file.buffer)
|
||||
else:
|
||||
view.command.exec("new_file")
|
||||
32
src/core/widgets/code/commands/move_to_right_sibling.py
Normal file
32
src/core/widgets/code/commands/move_to_right_sibling.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Move To Right Sibling Command")
|
||||
if not view.sibling_right: return
|
||||
|
||||
buffer = view.get_buffer()
|
||||
popped_file, next_file = view.files_manager.swap_file(buffer)
|
||||
|
||||
popped_file.remove_observer(view)
|
||||
popped_file.add_observer(view.sibling_right)
|
||||
view.sibling_right.set_buffer(buffer)
|
||||
view.sibling_right.grab_focus()
|
||||
|
||||
if next_file:
|
||||
next_file.add_observer(view)
|
||||
view.set_buffer(next_file.buffer)
|
||||
else:
|
||||
view.command.exec("new_file")
|
||||
33
src/core/widgets/code/commands/new_file.py
Normal file
33
src/core/widgets/code/commands/new_file.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Python imports
|
||||
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("New File Command")
|
||||
file = view.files_manager.new()
|
||||
language = view.language_manager \
|
||||
.guess_language("file.txt", None)
|
||||
|
||||
file.buffer.set_language(language)
|
||||
file.buffer.set_style_scheme(view.syntax_theme)
|
||||
|
||||
view.set_buffer(file.buffer)
|
||||
file.add_observer(view)
|
||||
|
||||
has_focus = view.command.exec("has_focus")
|
||||
if not has_focus: return file
|
||||
|
||||
view.command.exec("update_info_bar")
|
||||
return file
|
||||
33
src/core/widgets/code/commands/open_files.py
Normal file
33
src/core/widgets/code/commands/open_files.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Open File(s) Command")
|
||||
gfiles = event_system.emit_and_await("open-files")
|
||||
if not gfiles: return
|
||||
|
||||
size = len(gfiles)
|
||||
for i, gfile in enumerate(gfiles):
|
||||
file = view.files_manager.new()
|
||||
view.command.exec_with_args("load_file", (view, gfile, file))
|
||||
|
||||
if i == (size - 1):
|
||||
buffer = view.get_buffer()
|
||||
_file = view.files_manager.get_file(buffer)
|
||||
_file.remove_observer(view)
|
||||
|
||||
view.set_buffer(file.buffer)
|
||||
file.add_observer(view)
|
||||
view.command.exec("update_info_bar")
|
||||
28
src/core/widgets/code/commands/paste_temp_buffer.py
Normal file
28
src/core/widgets/code/commands/paste_temp_buffer.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GLib
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Paste Temp Buffer Command")
|
||||
|
||||
view.clear_temp_cut_buffer_delayed()
|
||||
|
||||
buffer = view.get_buffer()
|
||||
itr = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||
insert_itr = itr.copy()
|
||||
|
||||
buffer.insert(insert_itr, view._cut_buffer, -1)
|
||||
|
||||
view.set_temp_cut_buffer_delayed()
|
||||
30
src/core/widgets/code/commands/save_file.py
Normal file
30
src/core/widgets/code/commands/save_file.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Save File Command")
|
||||
buffer = view.get_buffer()
|
||||
file = view.files_manager.get_file(buffer)
|
||||
|
||||
if file.ftype == "buffer":
|
||||
file.save_as()
|
||||
language = view.language_manager \
|
||||
.guess_language(file.fname, None)
|
||||
file.ftype = language
|
||||
file.buffer.set_language(language)
|
||||
return
|
||||
|
||||
file.save()
|
||||
|
||||
28
src/core/widgets/code/commands/save_file_as.py
Normal file
28
src/core/widgets/code/commands/save_file_as.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.info("Save File As Command")
|
||||
buffer = view.get_buffer()
|
||||
file = view.files_manager.get_file(buffer)
|
||||
|
||||
file.save_as()
|
||||
|
||||
language = view.language_manager \
|
||||
.guess_language(file.fname, None)
|
||||
file.ftype = language
|
||||
file.buffer.set_language(language)
|
||||
file.add_observer(view)
|
||||
view.exec_command("update_info_bar")
|
||||
36
src/core/widgets/code/commands/set_buffer.py
Normal file
36
src/core/widgets/code/commands/set_buffer.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# Python imports
|
||||
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
from ..source_file import SourceFile
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
file: SourceFile
|
||||
):
|
||||
logger.debug("Set Buffer Command")
|
||||
|
||||
buffer = view.get_buffer()
|
||||
_file = view.files_manager.get_file(buffer)
|
||||
_file.remove_observer(view)
|
||||
|
||||
if not file:
|
||||
view.command.exec("new_file")
|
||||
return
|
||||
|
||||
view.set_buffer(file.buffer)
|
||||
file.add_observer(view)
|
||||
|
||||
has_focus = view.command.exec("has_focus")
|
||||
if has_focus:
|
||||
view.command.exec("update_info_bar")
|
||||
|
||||
23
src/core/widgets/code/commands/set_buffer_language.py
Normal file
23
src/core/widgets/code/commands/set_buffer_language.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
language: str
|
||||
):
|
||||
logger.debug("Set Buffer Language Command")
|
||||
|
||||
buffer = editor.get_buffer()
|
||||
buffer.set_language(
|
||||
view.language_manager.get_language(language)
|
||||
)
|
||||
23
src/core/widgets/code/commands/set_buffer_style.py
Normal file
23
src/core/widgets/code/commands/set_buffer_style.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
style: str
|
||||
):
|
||||
logger.debug("Set Buffer Style Command")
|
||||
|
||||
buffer = editor.get_buffer()
|
||||
buffer.set_style_scheme(
|
||||
view.style_scheme_manager.get_scheme(style)
|
||||
)
|
||||
26
src/core/widgets/code/commands/set_focus_border.py
Normal file
26
src/core/widgets/code/commands/set_focus_border.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Set Focus Border Command")
|
||||
ctx = view.get_parent().get_style_context()
|
||||
ctx.add_class("source-view-focused")
|
||||
|
||||
if view.sibling_right:
|
||||
ctx = view.sibling_right.get_parent().get_style_context()
|
||||
elif view.sibling_left:
|
||||
ctx = view.sibling_left.get_parent().get_style_context()
|
||||
|
||||
ctx.remove_class("source-view-focused")
|
||||
27
src/core/widgets/code/commands/set_miniview.py
Normal file
27
src/core/widgets/code/commands/set_miniview.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
from libs.dto.code_event import CodeEvent
|
||||
|
||||
from ..source_file import SourceFile
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
):
|
||||
logger.debug("Set MiniView Command")
|
||||
event_system.emit("set-mini-view", (view,))
|
||||
|
||||
event = CodeEvent()
|
||||
event.etype = "focused_view_change"
|
||||
event.view = view
|
||||
view.notify_observers(event)
|
||||
18
src/core/widgets/code/commands/show_completion.py
Normal file
18
src/core/widgets/code/commands/show_completion.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Show Completion Command")
|
||||
view.completion.request_completion()
|
||||
33
src/core/widgets/code/commands/update_info_bar.py
Normal file
33
src/core/widgets/code/commands/update_info_bar.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
from ..source_file import SourceFile
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View,
|
||||
):
|
||||
logger.debug("Update Info Bar Command")
|
||||
buffer = view.get_buffer()
|
||||
file = view.files_manager.get_file(buffer)
|
||||
|
||||
if not file: return
|
||||
|
||||
iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||
line = iter.get_line() + 1
|
||||
column = iter.get_line_offset()
|
||||
ftype = file.ftype.get_id() if hasattr(file.ftype, "get_id") else file.ftype
|
||||
|
||||
event_system.emit(
|
||||
"set-info-labels",
|
||||
(file.fpath, f"{line}:{column}", ftype, file.encoding)
|
||||
)
|
||||
23
src/core/widgets/code/commands/zoom_in.py
Normal file
23
src/core/widgets/code/commands/zoom_in.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Zoom In Command")
|
||||
|
||||
ctx = view.get_style_context()
|
||||
if view.zoom_level < 99:
|
||||
ctx.remove_class(f"px{view.zoom_level}")
|
||||
view.zoom_level += 1
|
||||
ctx.add_class(f"px{view.zoom_level}")
|
||||
23
src/core/widgets/code/commands/zoom_out.py
Normal file
23
src/core/widgets/code/commands/zoom_out.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
gi.require_version('GtkSource', '4')
|
||||
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
def execute(
|
||||
view: GtkSource.View = None
|
||||
):
|
||||
logger.debug("Zoom Out Command")
|
||||
|
||||
ctx = view.get_style_context()
|
||||
if view.zoom_level > 1:
|
||||
ctx.remove_class(f"px{view.zoom_level}")
|
||||
view.zoom_level -= 1
|
||||
ctx.add_class(f"px{view.zoom_level}")
|
||||
66
src/core/widgets/code/completion_manager.py
Normal file
66
src/core/widgets/code/completion_manager.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from .completion_providers.example_completion_provider import ExampleCompletionProvider
|
||||
from .completion_providers.lsp_completion_provider import LSPCompletionProvider
|
||||
|
||||
|
||||
|
||||
class CompletionManager():
|
||||
def __init__(self):
|
||||
super(CompletionManager, self).__init__()
|
||||
|
||||
self._lsp_provider = LSPCompletionProvider()
|
||||
self._timeout_id = None
|
||||
|
||||
|
||||
def set_completer(self, completer):
|
||||
self._completor = completer
|
||||
|
||||
def request_completion(self):
|
||||
if self._timeout_id:
|
||||
GLib.source_remove(self._timeout_id)
|
||||
|
||||
self._timeout_id = GLib.timeout_add(
|
||||
800,
|
||||
self._process_request_completion
|
||||
)
|
||||
|
||||
def _process_request_completion(self):
|
||||
self._start_completion()
|
||||
|
||||
self._timeout_id = None
|
||||
return False
|
||||
|
||||
def _do_completion(self):
|
||||
if self._completor.get_providers():
|
||||
self._mach_completion()
|
||||
else:
|
||||
self._start_completion()
|
||||
|
||||
def _mach_completion(self):
|
||||
"""
|
||||
Note: Use IF providers were added to completion...
|
||||
"""
|
||||
self._completion.match(
|
||||
self._completion.create_context()
|
||||
)
|
||||
|
||||
def _start_completion(self):
|
||||
"""
|
||||
Note: Use IF NO providers have been added to completion...
|
||||
print("here")
|
||||
"""
|
||||
self._completor.start(
|
||||
[
|
||||
ExampleCompletionProvider(),
|
||||
self._lsp_provider
|
||||
],
|
||||
self._completor.create_context()
|
||||
)
|
||||
|
||||
|
||||
3
src/core/widgets/code/completion_providers/__init__.py
Normal file
3
src/core/widgets/code/completion_providers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Custom Completion Providers Module
|
||||
"""
|
||||
@@ -0,0 +1,84 @@
|
||||
# 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)
|
||||
@@ -0,0 +1,137 @@
|
||||
# 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
|
||||
@@ -0,0 +1,107 @@
|
||||
# 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
|
||||
103
src/core/widgets/code/general_info_widget.py
Normal file
103
src/core/widgets/code/general_info_widget.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Pango
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class GeneralInfoWidget(Gtk.Box):
|
||||
""" docstring for StatusInfoWidget. """
|
||||
|
||||
def __init__(self):
|
||||
super(GeneralInfoWidget, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_margin_start(25)
|
||||
self.set_margin_end(25)
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("set-info-labels", self._set_info_labels)
|
||||
event_system.subscribe("set-path-label", self._set_path_label)
|
||||
event_system.subscribe("set-encoding-label", self._set_encoding_label)
|
||||
event_system.subscribe("set-line-char-label", self._set_line_char_label)
|
||||
event_system.subscribe("set-file-type-label", self._set_file_type_label)
|
||||
|
||||
|
||||
def _load_widgets(self):
|
||||
self.path_label = Gtk.Label(label = "...")
|
||||
self.line_char_label = Gtk.Label(label = "1:0")
|
||||
self.encoding_label = Gtk.Label(label = "utf-8")
|
||||
self.file_type_label = Gtk.Label(label = "buffer")
|
||||
|
||||
self.add(self.path_label)
|
||||
self.add(self.line_char_label)
|
||||
self.add(self.encoding_label)
|
||||
self.add(self.file_type_label)
|
||||
|
||||
self.path_label.set_hexpand(True)
|
||||
self.path_label.set_ellipsize(Pango.EllipsizeMode.START)
|
||||
self.path_label.set_single_line_mode(True)
|
||||
self.path_label.set_max_width_chars(48)
|
||||
|
||||
self.line_char_label.set_hexpand(True)
|
||||
self.encoding_label.set_hexpand(True)
|
||||
self.file_type_label.set_hexpand(True)
|
||||
|
||||
def _set_info_labels(
|
||||
self,
|
||||
path: Gio.File or str = None,
|
||||
line_char: str = None,
|
||||
file_type: str = None,
|
||||
encoding_type: str = None
|
||||
):
|
||||
self._set_path_label(path)
|
||||
self._set_line_char_label(line_char)
|
||||
self._set_file_type_label(file_type)
|
||||
self._set_encoding_label(encoding_type)
|
||||
|
||||
def _set_path_label(self, gfile: Gio.File or str = "..."):
|
||||
gfile = "" if not gfile else gfile
|
||||
|
||||
if isinstance(gfile, str):
|
||||
# path = gfile
|
||||
# path = "..." + path[-120: -1] if len(path) >= 123 else path
|
||||
# self.path_label.set_text( path )
|
||||
self.path_label.set_text( gfile )
|
||||
self.path_label.set_tooltip_text( gfile )
|
||||
else:
|
||||
self.path_label.set_text( gfile.get_path() )
|
||||
self.path_label.set_tooltip_text( gfile.get_path() )
|
||||
|
||||
def _set_line_char_label(self, line_char = "1:1"):
|
||||
line_char = "1:1" if not line_char else line_char
|
||||
|
||||
self.line_char_label.set_text(line_char)
|
||||
|
||||
def _set_file_type_label(self, file_type = "buffer"):
|
||||
file_type = "buffer" if not file_type else file_type
|
||||
|
||||
self.file_type_label.set_text(file_type)
|
||||
|
||||
def _set_encoding_label(self, encoding_type = "utf-8"):
|
||||
encoding_type = "utf-8" if not encoding_type else encoding_type
|
||||
|
||||
self.encoding_label.set_text(encoding_type)
|
||||
|
||||
|
||||
127
src/core/widgets/code/key_mapper.py
Normal file
127
src/core/widgets/code/key_mapper.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# Python imports
|
||||
import copy
|
||||
import json
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gdk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class NoKeyState:
|
||||
held: dict = {}
|
||||
released: dict = {}
|
||||
|
||||
class CtrlKeyState:
|
||||
held: dict = {}
|
||||
released: dict = {}
|
||||
|
||||
class ShiftKeyState:
|
||||
held: dict = {}
|
||||
released: dict = {}
|
||||
|
||||
class AltKeyState:
|
||||
held: dict = {}
|
||||
released: dict = {}
|
||||
|
||||
class CtrlShiftKeyState:
|
||||
held: dict = {}
|
||||
released: dict = {}
|
||||
|
||||
class CtrlAltKeyState:
|
||||
held: dict = {}
|
||||
released: dict = {}
|
||||
|
||||
class AltShiftKeyState:
|
||||
held: dict = {}
|
||||
released: dict = {}
|
||||
|
||||
class CtrlShiftAltKeyState:
|
||||
held: dict = {}
|
||||
released: dict = {}
|
||||
|
||||
|
||||
|
||||
class KeyMapper:
|
||||
def __init__(self):
|
||||
super(KeyMapper, self).__init__()
|
||||
|
||||
self.state = NoKeyState
|
||||
self._map = {
|
||||
NoKeyState: NoKeyState(),
|
||||
NoKeyState | CtrlKeyState : CtrlKeyState(),
|
||||
NoKeyState | ShiftKeyState: ShiftKeyState(),
|
||||
NoKeyState | AltKeyState : AltKeyState(),
|
||||
NoKeyState | CtrlKeyState | ShiftKeyState : CtrlShiftKeyState(),
|
||||
NoKeyState | CtrlKeyState | AltKeyState : CtrlAltKeyState(),
|
||||
NoKeyState | AltKeyState | ShiftKeyState : AltShiftKeyState(),
|
||||
NoKeyState | CtrlKeyState | ShiftKeyState | AltKeyState: CtrlShiftAltKeyState(),
|
||||
}
|
||||
|
||||
self.load_map()
|
||||
|
||||
|
||||
def load_map(self):
|
||||
self.states = copy.deepcopy(self._map)
|
||||
bindings_file = f"{settings_manager.path_manager.get_home_config_path()}/code-key-bindings.json"
|
||||
|
||||
with open(bindings_file, 'r') as f:
|
||||
data = json.load(f)["keybindings"]
|
||||
|
||||
for command in data:
|
||||
press_state = "held" if "held" in data[command] else "released"
|
||||
keyname = data[command][press_state]
|
||||
|
||||
state = NoKeyState
|
||||
if "<Control>" in keyname:
|
||||
state = state | CtrlKeyState
|
||||
if "<Shift>" in keyname:
|
||||
state = state | ShiftKeyState
|
||||
if "<Alt>" in keyname:
|
||||
state = state | AltKeyState
|
||||
|
||||
keyname = keyname.replace("<Control>", "") \
|
||||
.replace("<Shift>", "") \
|
||||
.replace("<Alt>", "") \
|
||||
.lower()
|
||||
|
||||
getattr(self.states[state], press_state)[keyname] = command
|
||||
|
||||
def re_map(self):
|
||||
self.states = copy.deepcopy(self._map)
|
||||
|
||||
def _key_press_event(self, eve):
|
||||
keyname = Gdk.keyval_name(eve.keyval).lower()
|
||||
|
||||
self._set_key_state(eve)
|
||||
if keyname in self.states[self.state].held:
|
||||
return self.states[self.state].held[keyname]
|
||||
|
||||
def _key_release_event(self, eve):
|
||||
keyname = Gdk.keyval_name(eve.keyval).lower()
|
||||
|
||||
self._set_key_state(eve)
|
||||
if keyname in self.states[self.state].released:
|
||||
return self.states[self.state].released[keyname]
|
||||
|
||||
def _set_key_state(self, eve):
|
||||
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
|
||||
is_control = True if modifiers & Gdk.ModifierType.CONTROL_MASK else False
|
||||
is_shift = True if modifiers & Gdk.ModifierType.SHIFT_MASK else False
|
||||
|
||||
try:
|
||||
is_alt = True if modifiers & Gdk.ModifierType.ALT_MASK else False
|
||||
except Exception:
|
||||
is_alt = True if modifiers & Gdk.ModifierType.MOD1_MASK else False
|
||||
|
||||
self.state = NoKeyState
|
||||
if is_control:
|
||||
self.state = self.state | CtrlKeyState
|
||||
if is_shift:
|
||||
self.state = self.state | ShiftKeyState
|
||||
if is_alt:
|
||||
self.state = self.state | AltKeyState
|
||||
|
||||
38
src/core/widgets/code/miniview_widget.py
Normal file
38
src/core/widgets/code/miniview_widget.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('GtkSource', '4')
|
||||
from gi.repository.GtkSource import Map
|
||||
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class MiniViewWidget(Map):
|
||||
def __init__(self):
|
||||
super(MiniViewWidget, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_hexpand(False)
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("mini-view")
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe(f"set-mini-view", self.set_smini_view)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def set_smini_view(self, source_view):
|
||||
self.set_view(source_view)
|
||||
3
src/core/widgets/code/mixins/__init__.py
Normal file
3
src/core/widgets/code/mixins/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Code Mixins Package
|
||||
"""
|
||||
40
src/core/widgets/code/mixins/source_view_dnd_mixin.py
Normal file
40
src/core/widgets/code/mixins/source_view_dnd_mixin.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class SourceViewDnDMixin:
|
||||
|
||||
def _set_up_dnd(self):
|
||||
PLAIN_TEXT_TARGET_TYPE = 70
|
||||
URI_TARGET_TYPE = 80
|
||||
text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE)
|
||||
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
|
||||
targets = [ text_target, uri_target ]
|
||||
|
||||
self.drag_dest_set_target_list(targets)
|
||||
|
||||
def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
||||
if info == 70: return
|
||||
|
||||
if info == 80:
|
||||
uris = data.get_uris()
|
||||
|
||||
if len(uris) == 0:
|
||||
uris = data.get_text().split("\n")
|
||||
|
||||
self._on_uri_data_received(uris)
|
||||
|
||||
def _on_uri_data_received(self, uris: []):
|
||||
uri = uris.pop(0)
|
||||
self.command.exec_with_args("dnd_load_file_to_buffer", (self, uri))
|
||||
|
||||
if len(uris) == 0: return
|
||||
|
||||
self.command.exec_with_args("dnd_load_files", (self, uris))
|
||||
63
src/core/widgets/code/mixins/source_view_events_mixin.py
Normal file
63
src/core/widgets/code/mixins/source_view_events_mixin.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from libs.dto.code_event import CodeEvent
|
||||
|
||||
|
||||
|
||||
class SourceViewEventsMixin:
|
||||
def _focus_in_event(self, view, eve):
|
||||
self.command.exec("set_miniview")
|
||||
self.command.exec("set_focus_border")
|
||||
self.command.exec("update_info_bar")
|
||||
|
||||
def _move_cursor(self, view, step, count, extend_selection):
|
||||
self.command.exec("update_info_bar")
|
||||
|
||||
def _button_press_event(self, view, eve):
|
||||
self.command.exec("update_info_bar")
|
||||
|
||||
def _button_release_event(self, view, eve):
|
||||
self.command.exec("update_info_bar")
|
||||
|
||||
def _key_press_event(self, view, eve):
|
||||
command = self.key_mapper._key_press_event(eve)
|
||||
if not command: return False
|
||||
|
||||
self.command.exec(command)
|
||||
return True
|
||||
|
||||
def _key_release_event(self, view, eve):
|
||||
command = self.key_mapper._key_release_event(eve)
|
||||
if not command: return False
|
||||
|
||||
self.command.exec(command)
|
||||
return True
|
||||
|
||||
def notification(self, event: CodeEvent):
|
||||
if not hasattr(self, "command"): return
|
||||
|
||||
has_focus = self.command.exec("has_focus")
|
||||
if not has_focus and not event.ignore_focus: return
|
||||
|
||||
match event.etype:
|
||||
case "removed_file":
|
||||
logger.debug("SourceFileManager.remove_file")
|
||||
if not event.file.buffer == self.get_buffer(): return
|
||||
self.command.exec_with_args("set_buffer", (self, event.next_file))
|
||||
return
|
||||
case "changed":
|
||||
logger.debug("SourceFile._changed")
|
||||
case "modified_changed":
|
||||
logger.debug("SourceFile._modified_changed")
|
||||
case "insert_text":
|
||||
logger.debug("SourceFile._insert_text")
|
||||
case "mark_set":
|
||||
# logger.debug("SourceFile._mark_set")
|
||||
...
|
||||
case _:
|
||||
...
|
||||
|
||||
self.command.exec("update_info_bar")
|
||||
40
src/core/widgets/code/source_buffer.py
Normal file
40
src/core/widgets/code/source_buffer.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('GtkSource', '4')
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class SourceBuffer(GtkSource.Buffer):
|
||||
def __init__(self):
|
||||
super(SourceBuffer, self).__init__()
|
||||
|
||||
self._handler_ids = []
|
||||
|
||||
|
||||
def set_signals(
|
||||
self,
|
||||
_changed,
|
||||
_mark_set,
|
||||
_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)
|
||||
]
|
||||
|
||||
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)
|
||||
137
src/core/widgets/code/source_file.py
Normal file
137
src/core/widgets/code/source_file.py
Normal file
@@ -0,0 +1,137 @@
|
||||
# Python imports
|
||||
import os
|
||||
|
||||
# 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 Gio
|
||||
|
||||
# Application imports
|
||||
from libs.mixins.observable_mixin import ObservableMixin
|
||||
from libs.dto.code_event import CodeEvent
|
||||
|
||||
from .source_buffer import SourceBuffer
|
||||
|
||||
|
||||
|
||||
class SourceFile(GtkSource.File, ObservableMixin):
|
||||
def __init__(self):
|
||||
super(SourceFile, self).__init__()
|
||||
|
||||
self.observers = []
|
||||
|
||||
self.encoding: str = "UTF-8"
|
||||
self.fname: str = "buffer"
|
||||
self.fpath: str = "buffer"
|
||||
self.ftype: str = "buffer"
|
||||
|
||||
self.buffer: SourceBuffer = SourceBuffer()
|
||||
|
||||
self._set_signals()
|
||||
|
||||
|
||||
def _set_signals(self):
|
||||
self.buffer.set_signals(
|
||||
self._changed,
|
||||
self._mark_set,
|
||||
self._insert_text,
|
||||
self._modified_changed
|
||||
)
|
||||
|
||||
|
||||
def _insert_text(self, buffer: SourceBuffer, location: Gtk.TextIter,
|
||||
text: str, length: int
|
||||
):
|
||||
event = CodeEvent()
|
||||
event.etype = "insert_text"
|
||||
event.file = self
|
||||
event.buffer = buffer
|
||||
|
||||
self.notify_observers(event)
|
||||
|
||||
def _changed(self, buffer: SourceBuffer):
|
||||
event = CodeEvent()
|
||||
event.etype = "changed"
|
||||
event.file = self
|
||||
event.buffer = buffer
|
||||
|
||||
self.notify_observers(event)
|
||||
|
||||
def _mark_set(self, buffer: SourceBuffer, location: Gtk.TextIter,
|
||||
mark: Gtk.TextMark
|
||||
):
|
||||
# event = CodeEvent()
|
||||
# event.etype = "mark_set"
|
||||
# event.file = self
|
||||
# event.buffer = buffer
|
||||
|
||||
# self.notify_observers(event)
|
||||
...
|
||||
|
||||
def _modified_changed(self, buffer: SourceBuffer):
|
||||
event = CodeEvent()
|
||||
event.etype = "modified_changed"
|
||||
event.file = self
|
||||
event.buffer = buffer
|
||||
|
||||
self.notify_observers(event)
|
||||
|
||||
|
||||
def _write_file(self, gfile: Gio.File):
|
||||
if not gfile: return
|
||||
|
||||
with open(gfile.get_path(), 'w') as f:
|
||||
start_itr, end_itr = self.buffer.get_bounds()
|
||||
text = self.buffer.get_text(start_itr, end_itr, True)
|
||||
|
||||
f.write(text)
|
||||
|
||||
return gfile
|
||||
|
||||
|
||||
def load_path(self, gfile: Gio.File):
|
||||
if not gfile: return
|
||||
|
||||
self.set_path(gfile)
|
||||
data = gfile.load_bytes()[0].get_data().decode("UTF-8")
|
||||
undo_manager = self.buffer.get_undo_manager()
|
||||
|
||||
undo_manager.begin_not_undoable_action()
|
||||
self.buffer.insert_at_cursor(data)
|
||||
undo_manager.end_not_undoable_action()
|
||||
|
||||
def set_path(self, gfile: Gio.File):
|
||||
if not gfile: return
|
||||
self.set_location(gfile)
|
||||
|
||||
self.fpath = gfile.get_path()
|
||||
self.fname = gfile.get_basename()
|
||||
|
||||
event = CodeEvent()
|
||||
event.etype = "set_path"
|
||||
event.file = self
|
||||
|
||||
self.notify_observers(event)
|
||||
|
||||
def save(self):
|
||||
self._write_file( self.get_location() )
|
||||
|
||||
def save_as(self):
|
||||
file = event_system.emit_and_await("save-file-dialog")
|
||||
if not file: return
|
||||
|
||||
self._write_file(file)
|
||||
self.set_path(file)
|
||||
|
||||
return file
|
||||
|
||||
def close(self):
|
||||
self.observers.clear()
|
||||
|
||||
del self.observers
|
||||
del self.buffer
|
||||
106
src/core/widgets/code/source_files_manager.py
Normal file
106
src/core/widgets/code/source_files_manager.py
Normal file
@@ -0,0 +1,106 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from libs.mixins.observable_mixin import ObservableMixin
|
||||
from libs.singleton import Singleton
|
||||
from libs.dto.code_event import CodeEvent
|
||||
|
||||
from .source_file import SourceFile
|
||||
from .source_buffer import SourceBuffer
|
||||
|
||||
|
||||
|
||||
class SourceFilesManager(Singleton, list, ObservableMixin):
|
||||
def __init__(self):
|
||||
super(SourceFilesManager, self).__init__()
|
||||
|
||||
self.observers = []
|
||||
|
||||
|
||||
def new(self):
|
||||
file = SourceFile()
|
||||
self.append(file)
|
||||
return file
|
||||
|
||||
def append(self, file: SourceFile):
|
||||
if not file: return
|
||||
super().append(file)
|
||||
|
||||
event = CodeEvent()
|
||||
event.etype = "appended_file"
|
||||
event.file = file
|
||||
|
||||
self.notify_observers(event)
|
||||
|
||||
def get_file(self, buffer: SourceBuffer):
|
||||
if not buffer: return
|
||||
|
||||
for file in self:
|
||||
if not buffer == file.buffer: continue
|
||||
return file
|
||||
|
||||
def pop_file(self, buffer: SourceBuffer):
|
||||
if not buffer: return
|
||||
|
||||
for i, file in enumerate(self):
|
||||
if not buffer == file.buffer: continue
|
||||
|
||||
j = self.next_index(i)
|
||||
next_file = self[j] if not j == -1 else None
|
||||
popped_file = self.pop(i)
|
||||
event = CodeEvent()
|
||||
event.etype = "popped_file"
|
||||
event.file = popped_file
|
||||
event.next_file = next_file
|
||||
|
||||
self.notify_observers(event)
|
||||
|
||||
return popped_file, next_file
|
||||
|
||||
def swap_file(self, buffer: SourceBuffer):
|
||||
if not buffer: return
|
||||
|
||||
for i, file in enumerate(self):
|
||||
if not buffer == file.buffer: continue
|
||||
|
||||
j = self.next_index(i)
|
||||
next_file = self[j]
|
||||
swapped_file = self[j] if not j == -1 else None
|
||||
|
||||
return swapped_file, next_file
|
||||
|
||||
def remove_file(self, buffer: SourceBuffer):
|
||||
if not buffer: return
|
||||
|
||||
for i, file in enumerate(self):
|
||||
if not buffer == file.buffer: continue
|
||||
|
||||
j = self.next_index(i)
|
||||
next_file = self[j] if not j == -1 else None
|
||||
event = CodeEvent()
|
||||
event.etype = "removed_file"
|
||||
event.ignore_focus = True
|
||||
event.file = file
|
||||
event.next_file = next_file
|
||||
self.notify_observers(event)
|
||||
|
||||
self.remove(file)
|
||||
file.close()
|
||||
|
||||
return next_file
|
||||
|
||||
def next_index(self, i):
|
||||
size = len(self)
|
||||
|
||||
if (i == 0) & (size >= 2):
|
||||
j = i + 1
|
||||
elif (i == (size - 1)) & (size >= 2):
|
||||
j = i - 1
|
||||
elif (size - 1) == 0:
|
||||
j = -1
|
||||
else:
|
||||
j = i + 1
|
||||
|
||||
return j
|
||||
61
src/core/widgets/code/tab_widget.py
Normal file
61
src/core/widgets/code/tab_widget.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class TabWidget(Gtk.Box):
|
||||
def __init__(self):
|
||||
super(TabWidget, self).__init__()
|
||||
|
||||
self.file = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("tab-widget")
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
self._label_eve_box = Gtk.EventBox()
|
||||
self.label = Gtk.Label(label = "")
|
||||
self.close_btn = Gtk.Button(label = "X")
|
||||
|
||||
ctx = self.label.get_style_context()
|
||||
ctx.add_class("tab-label")
|
||||
ctx = self.close_btn.get_style_context()
|
||||
ctx.add_class("tab-close-bttn")
|
||||
|
||||
self.label.set_hexpand(True)
|
||||
|
||||
self._label_eve_box.add(self.label)
|
||||
self.add(self._label_eve_box)
|
||||
self.add(self.close_btn)
|
||||
|
||||
def clear_signals_and_data(self):
|
||||
del self.file
|
||||
self._label_eve_box.disconnect(self._label_eve_box_id)
|
||||
self.close_btn.disconnect(self.close_btn_id)
|
||||
|
||||
def set_select_signal(self, callback):
|
||||
self._label_eve_box_id = self._label_eve_box.connect('button-release-event', callback, self.file)
|
||||
|
||||
def set_close_signal(self, callback):
|
||||
self.close_btn_id = self.close_btn.connect('button-release-event', callback, self.file)
|
||||
107
src/core/widgets/code/tabs_widget.py
Normal file
107
src/core/widgets/code/tabs_widget.py
Normal file
@@ -0,0 +1,107 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from libs.dto.code_event import CodeEvent
|
||||
|
||||
from .view import SourceView
|
||||
from .source_files_manager import SourceFilesManager
|
||||
from .source_file import SourceFile
|
||||
|
||||
from .tab_widget import TabWidget
|
||||
|
||||
|
||||
|
||||
class TabsWidget(Gtk.ScrolledWindow):
|
||||
def __init__(self):
|
||||
super(TabsWidget, self).__init__()
|
||||
|
||||
self.active_view: SourceView = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_overlay_scrolling(False)
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("register-view-to-tabs-widget", self._register_view_to_tabs_widget)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
self.files_manager: SourceFilesManager = SourceFilesManager()
|
||||
self.files_manager.add_observer(self)
|
||||
|
||||
def _load_widgets(self):
|
||||
self.viewport = Gtk.Viewport()
|
||||
self.tabs = Gtk.ButtonBox()
|
||||
|
||||
self.tabs.set_layout(Gtk.ButtonBoxStyle.CENTER)
|
||||
|
||||
self.viewport.add(self.tabs)
|
||||
self.add(self.viewport)
|
||||
|
||||
def _register_view_to_tabs_widget(self, view: SourceView):
|
||||
view.add_observer(self)
|
||||
view.set_files_manager(self.files_manager)
|
||||
|
||||
def notification(self, event: CodeEvent):
|
||||
match event.etype:
|
||||
case "focused_view_change":
|
||||
logger.debug("SourceView.focused_view_change")
|
||||
self.active_view = event.view
|
||||
case "appended_file":
|
||||
logger.debug("SourceFilesManager.appended")
|
||||
self.add_tab(event)
|
||||
case "popped_file":
|
||||
logger.debug("SourceFilesManager.pop_file")
|
||||
case "removed_file":
|
||||
logger.debug("SourceFilesManager.remove_file")
|
||||
self.remove_tab(event)
|
||||
case "set_path":
|
||||
logger.debug("SourceFile.set_path")
|
||||
self.update_tab_label(event)
|
||||
case _:
|
||||
...
|
||||
|
||||
def add_tab(self, event: CodeEvent):
|
||||
tab = TabWidget()
|
||||
tab.file = event.file
|
||||
|
||||
tab.label.set_label(event.file.fname)
|
||||
event.file.add_observer(self)
|
||||
|
||||
def select_signal(widget, eve, file):
|
||||
self.active_view.command.exec_with_args("set_buffer", (self.active_view, file))
|
||||
|
||||
def close_signal(widget, eve, file):
|
||||
self.files_manager.remove_file(file.buffer)
|
||||
|
||||
tab.set_select_signal(select_signal)
|
||||
tab.set_close_signal(close_signal)
|
||||
|
||||
self.tabs.add(tab)
|
||||
|
||||
def remove_tab(self, event: CodeEvent):
|
||||
for child in self.tabs.get_children():
|
||||
if not child.file == event.file: continue
|
||||
|
||||
child.file.remove_observer(self)
|
||||
self.tabs.remove(child)
|
||||
child.clear_signals_and_data()
|
||||
del child
|
||||
|
||||
return
|
||||
|
||||
def update_tab_label(self, event: CodeEvent):
|
||||
for tab in self.tabs.get_children():
|
||||
if not tab.file == event.file: continue
|
||||
tab.label.set_label(event.file.fname)
|
||||
|
||||
return
|
||||
130
src/core/widgets/code/view.py
Normal file
130
src/core/widgets/code/view.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# 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 GLib
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
from libs.mixins.observable_mixin import ObservableMixin
|
||||
|
||||
from .mixins.source_view_events_mixin import SourceViewEventsMixin
|
||||
from .mixins.source_view_dnd_mixin import SourceViewDnDMixin
|
||||
|
||||
from .source_files_manager import SourceFilesManager
|
||||
from .completion_manager import CompletionManager
|
||||
from .command_system import CommandSystem
|
||||
from .key_mapper import KeyMapper
|
||||
|
||||
|
||||
|
||||
class SourceView(GtkSource.View, ObservableMixin, SourceViewEventsMixin, SourceViewDnDMixin):
|
||||
def __init__(self):
|
||||
super(SourceView, self).__init__()
|
||||
|
||||
self.observers = []
|
||||
self._cut_temp_timeout_id = None
|
||||
self._cut_buffer = ""
|
||||
|
||||
self.sibling_right = None
|
||||
self.sibling_left = None
|
||||
|
||||
self._setup_styles()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styles(self):
|
||||
self.zoom_level = settings_manager.settings.theming.default_zoom
|
||||
ctx = self.get_style_context()
|
||||
|
||||
ctx.add_class("source-view")
|
||||
ctx.add_class(f"px{self.zoom_level}")
|
||||
|
||||
self.set_vexpand(True)
|
||||
self.set_bottom_margin(800)
|
||||
|
||||
self.set_show_line_marks(True)
|
||||
self.set_show_line_numbers(True)
|
||||
self.set_smart_backspace(True)
|
||||
self.set_indent_on_tab(True)
|
||||
self.set_insert_spaces_instead_of_tabs(True)
|
||||
self.set_auto_indent(True)
|
||||
self.set_monospace(True)
|
||||
self.set_tab_width(4)
|
||||
self.set_show_right_margin(True)
|
||||
self.set_right_margin_position(80)
|
||||
self.set_background_pattern(0) # 0 = None, 1 = Grid
|
||||
self.set_highlight_current_line(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.map_id = self.connect("map", self._init_map)
|
||||
|
||||
self.connect("focus-in-event", self._focus_in_event)
|
||||
self.connect("drag-data-received", self._on_drag_data_received)
|
||||
self.connect("move-cursor", self._move_cursor)
|
||||
self.connect("key-press-event", self._key_press_event)
|
||||
self.connect("key-release-event", self._key_release_event)
|
||||
self.connect("button-press-event", self._button_press_event)
|
||||
self.connect("button-release-event", self._button_release_event)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
self._set_up_dnd()
|
||||
event_system.emit("register-view-to-tabs-widget", (self,))
|
||||
|
||||
def _init_map(self, view):
|
||||
self.disconnect(self.map_id)
|
||||
del self.map_id
|
||||
|
||||
GLib.idle_add(self._init_show)
|
||||
|
||||
def _init_show(self):
|
||||
self.language_manager = GtkSource.LanguageManager()
|
||||
self.style_scheme_manager = GtkSource.StyleSchemeManager()
|
||||
|
||||
self.key_mapper = KeyMapper()
|
||||
self.command = CommandSystem()
|
||||
self.completion = CompletionManager()
|
||||
|
||||
self.command.set_data(self)
|
||||
self.completion.set_completer( self.get_completion() )
|
||||
|
||||
self.style_scheme_manager.append_search_path(
|
||||
f"{settings_manager.path_manager.get_home_config_path()}/code_styles"
|
||||
)
|
||||
self.syntax_theme = self.style_scheme_manager.get_scheme(
|
||||
f"{settings_manager.settings.theming.syntax_theme}"
|
||||
)
|
||||
|
||||
self.command.exec("new_file")
|
||||
|
||||
if not self.sibling_right: return
|
||||
|
||||
self.grab_focus()
|
||||
self.command.exec("load_start_files")
|
||||
|
||||
return False
|
||||
|
||||
def set_files_manager(self, files_manager: SourceFilesManager):
|
||||
self.files_manager = files_manager
|
||||
self.files_manager.add_observer(self)
|
||||
|
||||
def clear_temp_cut_buffer_delayed(self):
|
||||
if self._cut_temp_timeout_id:
|
||||
GLib.source_remove(self._cut_temp_timeout_id)
|
||||
|
||||
def set_temp_cut_buffer_delayed(self):
|
||||
def clear_temp_buffer():
|
||||
self._cut_buffer = ""
|
||||
self._cut_temp_timeout_id = None
|
||||
return False
|
||||
|
||||
self._cut_temp_timeout_id = GLib.timeout_add(15000, clear_temp_buffer)
|
||||
3
src/core/widgets/controls/__init__.py
Normal file
3
src/core/widgets/controls/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Widgets.Controls Package
|
||||
"""
|
||||
83
src/core/widgets/controls/open_files_button.py
Normal file
83
src/core/widgets/controls/open_files_button.py
Normal file
@@ -0,0 +1,83 @@
|
||||
# Python imports
|
||||
from contextlib import suppress
|
||||
import os
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class OpenFilesButton(Gtk.Button):
|
||||
"""docstring for OpenFilesButton."""
|
||||
|
||||
def __init__(self):
|
||||
super(OpenFilesButton, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_label("Open File(s)...")
|
||||
self.set_image( Gtk.Image.new_from_icon_name("gtk-open", 4) )
|
||||
self.set_always_show_image(True)
|
||||
self.set_image_position(1) # Left - 0, Right = 1
|
||||
self.set_hexpand(False)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("button-release-event", self._open_files)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("open-files", self._open_files)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def _open_files(self, widget = None, eve = None, gfile = None):
|
||||
start_dir = None
|
||||
_gfiles = []
|
||||
|
||||
if gfile and gfile.query_exists():
|
||||
start_dir = gfile.get_parent()
|
||||
|
||||
chooser = Gtk.FileChooserDialog("Open File(s)...", None,
|
||||
Gtk.FileChooserAction.OPEN,
|
||||
(
|
||||
Gtk.STOCK_CANCEL,
|
||||
Gtk.ResponseType.CANCEL,
|
||||
Gtk.STOCK_OPEN,
|
||||
Gtk.ResponseType.OK
|
||||
)
|
||||
)
|
||||
|
||||
chooser.set_select_multiple(True)
|
||||
|
||||
with suppress(Exception):
|
||||
folder = widget.get_current_file().get_parent() if not start_dir else start_dir
|
||||
chooser.set_current_folder( folder.get_path() )
|
||||
|
||||
response = chooser.run()
|
||||
if not response == Gtk.ResponseType.OK:
|
||||
chooser.destroy()
|
||||
return _gfiles
|
||||
|
||||
filenames = chooser.get_filenames()
|
||||
if not filenames:
|
||||
chooser.destroy()
|
||||
return _gfiles
|
||||
|
||||
for file in filenames:
|
||||
path = file if os.path.isabs(file) else os.path.abspath(file)
|
||||
_gfiles.append( Gio.File.new_for_path(path) )
|
||||
|
||||
chooser.destroy()
|
||||
|
||||
logger.debug(_gfiles)
|
||||
return _gfiles
|
||||
72
src/core/widgets/controls/save_as_button.py
Normal file
72
src/core/widgets/controls/save_as_button.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Python imports
|
||||
import os
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class SaveAsButton(Gtk.Button):
|
||||
def __init__(self):
|
||||
super(SaveAsButton, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_label("Save As")
|
||||
self.set_image( Gtk.Image.new_from_icon_name("gtk-save-as", 4) )
|
||||
self.set_always_show_image(True)
|
||||
self.set_image_position(1) # Left - 0, Right = 1
|
||||
self.set_hexpand(False)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("released", self._save_as)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("save-as", self._save_as)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def _save_as(self, widget = None, eve = None, gfile = None):
|
||||
start_dir = None
|
||||
_gfile = None
|
||||
|
||||
chooser = Gtk.FileChooserDialog("Save File As...", None,
|
||||
Gtk.FileChooserAction.SAVE,
|
||||
(
|
||||
Gtk.STOCK_CANCEL,
|
||||
Gtk.ResponseType.CANCEL,
|
||||
Gtk.STOCK_SAVE_AS,
|
||||
Gtk.ResponseType.OK
|
||||
)
|
||||
)
|
||||
|
||||
# chooser.set_select_multiple(False)
|
||||
|
||||
response = chooser.run()
|
||||
if not response == Gtk.ResponseType.OK:
|
||||
chooser.destroy()
|
||||
return _gfile
|
||||
|
||||
file = chooser.get_filename()
|
||||
if not file:
|
||||
chooser.destroy()
|
||||
return _gfile
|
||||
|
||||
path = file if os.path.isabs(file) else os.path.abspath(file)
|
||||
_gfile = Gio.File.new_for_path(path)
|
||||
|
||||
chooser.destroy()
|
||||
|
||||
logger.debug(f"File To Save As: {_gfile}")
|
||||
return _gfile
|
||||
48
src/core/widgets/controls/transparency_scale.py
Normal file
48
src/core/widgets/controls/transparency_scale.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class TransparencyScale(Gtk.Scale):
|
||||
def __init__(self):
|
||||
super(TransparencyScale, self).__init__()
|
||||
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_digits(0)
|
||||
self.set_value_pos(Gtk.PositionType.RIGHT)
|
||||
self.add_mark(50.0, Gtk.PositionType.TOP, "50%")
|
||||
self.set_hexpand(True)
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("value-changed", self._update_transparency)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
adjust = self.get_adjustment()
|
||||
adjust.set_lower(0)
|
||||
adjust.set_upper(100)
|
||||
adjust.set_value(settings_manager.settings.theming.transparency)
|
||||
adjust.set_step_increment(1.0)
|
||||
|
||||
def _update_transparency(self, range):
|
||||
event_system.emit("remove-transparency")
|
||||
tp = int(range.get_value())
|
||||
settings_manager.settings.theming.transparency = tp
|
||||
event_system.emit("update-transparency")
|
||||
66
src/core/widgets/save_file_dialog.py
Normal file
66
src/core/widgets/save_file_dialog.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class SaveFileDialog:
|
||||
"""docstring for SaveFileDialog."""
|
||||
|
||||
def __init__(self):
|
||||
super(SaveFileDialog, 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("save-file-dialog", self.save_file_dialog)
|
||||
|
||||
def _load_widgets(self):
|
||||
self._file_filter_text = Gtk.FileFilter()
|
||||
self._file_filter_text.set_name("Code Files")
|
||||
|
||||
for p in settings_manager.settings.filters.code:
|
||||
self._file_filter_text.add_pattern(p)
|
||||
|
||||
self._file_filter_all = Gtk.FileFilter()
|
||||
self._file_filter_all.set_name("All Files")
|
||||
self._file_filter_all.add_pattern("*.*")
|
||||
|
||||
|
||||
def save_file_dialog(self, current_filename: str = "", current_file: Gio.File = None) -> str:
|
||||
dlg = Gtk.FileChooserDialog(title = "Please choose a file...", parent = None, action = 1)
|
||||
|
||||
dlg.add_buttons("Cancel", Gtk.ResponseType.CANCEL, "Save", Gtk.ResponseType.OK)
|
||||
dlg.set_do_overwrite_confirmation(True)
|
||||
dlg.add_filter(self._file_filter_text)
|
||||
dlg.add_filter(self._file_filter_all)
|
||||
|
||||
if current_filename == "":
|
||||
import os
|
||||
dlg.set_current_name("new.txt")
|
||||
dlg.set_current_folder(os.path.expanduser('~'))
|
||||
else:
|
||||
dlg.set_current_folder(current_file.get_parent().get_uri())
|
||||
dlg.set_current_name(current_filename)
|
||||
|
||||
response = dlg.run()
|
||||
file = dlg.get_filename() if response == Gtk.ResponseType.OK else ""
|
||||
dlg.destroy()
|
||||
|
||||
return Gio.File.new_for_path(file) if not file == "" else None
|
||||
37
src/core/widgets/separator_widget.py
Normal file
37
src/core/widgets/separator_widget.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class Separator(Gtk.Separator):
|
||||
def __init__(self, id: str = None, ORIENTATION: int = 0):
|
||||
super(Separator, self).__init__()
|
||||
|
||||
builder = settings_manager.get_builder()
|
||||
if id:
|
||||
builder.expose_object(id, self)
|
||||
|
||||
self.ORIENTATION = ORIENTATION
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._load_widgets()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
# HORIZONTAL = 0, VERTICAL = 1
|
||||
self.set_orientation(self.ORIENTATION)
|
||||
|
||||
|
||||
def _setup_signals(self):
|
||||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
128
src/core/widgets/vte_widget.py
Normal file
128
src/core/widgets/vte_widget.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# Python imports
|
||||
import os
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
gi.require_version('Vte', '2.91')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Vte
|
||||
|
||||
# Application imports
|
||||
from libs.dto.event import Event
|
||||
|
||||
|
||||
|
||||
class VteWidgetException(Exception):
|
||||
...
|
||||
|
||||
|
||||
|
||||
class VteWidget(Vte.Terminal):
|
||||
"""
|
||||
https://stackoverflow.com/questions/60454326/how-to-implement-a-linux-terminal-in-a-pygtk-app-like-vscode-and-pycharm-has
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(VteWidget, self).__init__()
|
||||
|
||||
self.cd_cmd_prefix = ("cd".encode(), "cd ".encode())
|
||||
self.dont_process = False
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
self._do_session_spawn()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("vte-widget")
|
||||
|
||||
self.set_clear_background(False)
|
||||
self.set_enable_sixel(True)
|
||||
self.set_cursor_shape( Vte.CursorShape.IBEAM )
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("commit", self._commit)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("update_term_path", self.update_term_path)
|
||||
|
||||
def _load_widgets(self):
|
||||
...
|
||||
|
||||
def _do_session_spawn(self):
|
||||
self.spawn_sync(
|
||||
Vte.PtyFlags.DEFAULT,
|
||||
settings_manager.path_manager.get_home_path(),
|
||||
["/bin/bash"],
|
||||
[],
|
||||
GLib.SpawnFlags.DEFAULT,
|
||||
None, None,
|
||||
)
|
||||
|
||||
# Note: '-->:' is used as a delimiter to split on to get command actual.
|
||||
# !!! DO NOT REMOVE UNLESS CODE UPDATED ACCORDINGLY !!!
|
||||
startup_cmds = [
|
||||
"env -i /bin/bash --noprofile --norc\n",
|
||||
"export TERM='xterm-256color'\n",
|
||||
"export LC_ALL=C\n",
|
||||
"export XDG_RUNTIME_DIR='/run/user/1000'\n",
|
||||
"export DISPLAY=:0\n",
|
||||
f"export XAUTHORITY='{settings_manager.path_manager.get_home_path()}/.Xauthority'\n",
|
||||
f"\nexport HOME='{settings_manager.path_manager.get_home_path()}'\n",
|
||||
"export PS1='\\h@\\u \\W -->: '\n",
|
||||
"clear\n"
|
||||
]
|
||||
|
||||
for i in startup_cmds:
|
||||
self.run_command(i)
|
||||
|
||||
def _commit(self, terminal, text, size):
|
||||
if self.dont_process:
|
||||
self.dont_process = False
|
||||
return
|
||||
|
||||
if not text.encode() == "\r".encode(): return
|
||||
|
||||
text, attributes = self.get_text()
|
||||
|
||||
if not text: return
|
||||
|
||||
lines = text.strip().splitlines()
|
||||
command_ran = None
|
||||
|
||||
try:
|
||||
command_ran = lines[-1].split("-->:")[1].strip()
|
||||
except VteWidgetException as e:
|
||||
logger.debug(e)
|
||||
return
|
||||
|
||||
if not command_ran[0:3].encode() in self.cd_cmd_prefix:
|
||||
return
|
||||
|
||||
target_path = command_ran.split( command_ran[0:3] )[1]
|
||||
if target_path in (".", "./"): return
|
||||
|
||||
if not target_path:
|
||||
target_path = settings_manager.get_home_path()
|
||||
|
||||
event = Event("pty_path_updated", "", target_path)
|
||||
event_system.emit("handle_bridge_event", (event,))
|
||||
|
||||
def update_term_path(self, fpath: str):
|
||||
self.dont_process = True
|
||||
|
||||
cmds = [f"cd '{fpath}'\n", "clear\n"]
|
||||
for cmd in cmds:
|
||||
self.run_command(cmd)
|
||||
|
||||
def run_command(self, cmd: str):
|
||||
self.feed_child_binary(bytes(cmd, 'utf8'))
|
||||
3
src/core/widgets/webkit/__init__.py
Normal file
3
src/core/widgets/webkit/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
WebKit2 UI Package
|
||||
"""
|
||||
69
src/core/widgets/webkit/webkit_ui.py
Normal file
69
src/core/widgets/webkit/webkit_ui.py
Normal file
@@ -0,0 +1,69 @@
|
||||
# Python imports
|
||||
import json
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gdk', '3.0')
|
||||
gi.require_version('WebKit2', '4.0')
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import WebKit2
|
||||
|
||||
# Application imports
|
||||
from libs.settings.other.webkit_ui_settings import WebkitUISettings
|
||||
from libs.dto.event import Event
|
||||
|
||||
|
||||
class WebkitUI(WebKit2.WebView):
|
||||
def __init__(self):
|
||||
super(WebkitUI, self).__init__()
|
||||
|
||||
self._setup_styling()
|
||||
self._subscribe_to_events()
|
||||
self._load_view()
|
||||
self._setup_content_manager()
|
||||
|
||||
self.show_all()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_vexpand(True)
|
||||
self.set_hexpand(True)
|
||||
self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) )
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe(f"ui-message", self.ui_message)
|
||||
|
||||
def _load_settings(self):
|
||||
self.set_settings( WebkitUISettings() )
|
||||
|
||||
def _load_view(self):
|
||||
path = settings_manager.get_context_path()
|
||||
data = None
|
||||
|
||||
with open(f"{path}/index.html", "r") as f:
|
||||
data = f.read()
|
||||
|
||||
self.load_html(content = data, base_uri = f"file://{path}/")
|
||||
|
||||
def _setup_content_manager(self):
|
||||
content_manager = self.get_user_content_manager()
|
||||
content_manager.connect("script-message-received", self._process_js_message)
|
||||
content_manager.register_script_message_handler("backend")
|
||||
|
||||
def _process_js_message(self, user_content_manager, js_result):
|
||||
js_value = js_result.get_js_value()
|
||||
message = js_value.to_string()
|
||||
|
||||
try:
|
||||
event = Event( **json.loads(message) )
|
||||
event_system.emit("handle-bridge-event", (event,))
|
||||
except Exception as e:
|
||||
logger.info(e)
|
||||
|
||||
def ui_message(self, message, mtype):
|
||||
command = f"displayMessage('{message}', '{mtype}', '3')"
|
||||
self.run_javascript(command, None, None)
|
||||
|
||||
def run_javascript(self, script, cancellable, callback):
|
||||
logger.debug(script)
|
||||
super().run_javascript(script, cancellable, callback)
|
||||
200
src/core/window.py
Normal file
200
src/core/window.py
Normal file
@@ -0,0 +1,200 @@
|
||||
# Python imports
|
||||
import signal
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
import cairo
|
||||
gi.require_version('Gtk', '3.0')
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
|
||||
try:
|
||||
from gi.repository import GdkX11
|
||||
except ImportError:
|
||||
logger.debug("Could not import X11 gir module...")
|
||||
|
||||
# Application imports
|
||||
from libs.status_icon import StatusIcon
|
||||
from core.controllers.base_controller import BaseController
|
||||
|
||||
|
||||
|
||||
class ControllerStartExceptiom(Exception):
|
||||
...
|
||||
|
||||
|
||||
|
||||
class Window(Gtk.ApplicationWindow):
|
||||
""" docstring for Window. """
|
||||
|
||||
def __init__(self):
|
||||
super(Window, self).__init__()
|
||||
settings_manager.set_main_window(self)
|
||||
|
||||
self._status_icon = None
|
||||
self._controller = None
|
||||
|
||||
self.guake_key = settings_manager.get_guake_key()
|
||||
self.hidefunc = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
self._set_window_data()
|
||||
self._set_size_constraints()
|
||||
self._setup_window_toggle_event()
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_title(f"{APP_NAME}")
|
||||
self.set_icon_from_file( settings_manager.path_manager.get_window_icon() )
|
||||
self.set_decorated(True)
|
||||
self.set_skip_pager_hint(False)
|
||||
self.set_skip_taskbar_hint(False)
|
||||
self.set_gravity(5) # 5 = CENTER
|
||||
self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS
|
||||
|
||||
ctx = self.get_style_context()
|
||||
ctx.add_class("main-window")
|
||||
ctx.add_class(f"mw_transparency_{settings_manager.settings.theming.transparency}")
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("focus-in-event", self._on_focus_in_event)
|
||||
self.connect("focus-out-event", self._on_focus_out_event)
|
||||
|
||||
self.connect("delete-event", self.stop)
|
||||
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.stop)
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("tear-down", self.stop)
|
||||
event_system.subscribe("load-interactive-debug", self._load_interactive_debug)
|
||||
|
||||
def _load_widgets(self):
|
||||
if settings_manager.is_debug():
|
||||
self.set_interactive_debugging(True)
|
||||
|
||||
self._controller = BaseController()
|
||||
self._status_icon = StatusIcon()
|
||||
if not self._controller:
|
||||
raise ControllerStartException("BaseController exited and doesn't exist...")
|
||||
|
||||
self.add( self._controller.get_base_container() )
|
||||
|
||||
def _display_manager(self):
|
||||
""" Try to detect which display manager we are running under... """
|
||||
|
||||
import os
|
||||
if os.environ.get('WAYLAND_DISPLAY'):
|
||||
return 'WAYLAND'
|
||||
|
||||
return 'X11'
|
||||
|
||||
def _set_size_constraints(self):
|
||||
_window_x = settings_manager.settings.config.main_window_x
|
||||
_window_y = settings_manager.settings.config.main_window_y
|
||||
_min_width = settings_manager.settings.config.main_window_min_width
|
||||
_min_height = settings_manager.settings.config.main_window_min_height
|
||||
_width = settings_manager.settings.config.main_window_width
|
||||
_height = settings_manager.settings.config.main_window_height
|
||||
|
||||
self.move(_window_x, _window_y - 28)
|
||||
self.set_size_request(_min_width, _min_height)
|
||||
self.set_default_size(_width, _height)
|
||||
|
||||
def _set_window_data(self) -> None:
|
||||
screen = self.get_screen()
|
||||
visual = screen.get_rgba_visual()
|
||||
|
||||
if visual and screen.is_composited() and settings_manager.settings.config.make_transparent == 0:
|
||||
self.set_visual(visual)
|
||||
self.set_app_paintable(True)
|
||||
# self.connect("draw", self._area_draw)
|
||||
|
||||
# bind css file
|
||||
cssProvider = Gtk.CssProvider()
|
||||
styleContext = Gtk.StyleContext()
|
||||
cssProvider.load_from_path( settings_manager.path_manager.get_css_file() )
|
||||
styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
|
||||
def _area_draw(self, widget: Gtk.ApplicationWindow, cr: cairo.Context) -> None:
|
||||
cr.set_source_rgba( *settings_manager.get_paint_bg_color() )
|
||||
cr.set_operator(cairo.OPERATOR_SOURCE)
|
||||
cr.paint()
|
||||
cr.set_operator(cairo.OPERATOR_OVER)
|
||||
|
||||
|
||||
def _on_focus_in_event(self, widget, event):
|
||||
event_system.emit("pause-dnd-signals")
|
||||
|
||||
def _on_focus_out_event(self, widget, event):
|
||||
event_system.emit("listen-dnd-signals")
|
||||
|
||||
def _load_interactive_debug(self):
|
||||
self.set_interactive_debugging(True)
|
||||
|
||||
def _setup_window_toggle_event(self) -> None:
|
||||
hidebound = None
|
||||
if not self.guake_key or not self._display_manager() == 'X11':
|
||||
return
|
||||
|
||||
try:
|
||||
import gi
|
||||
gi.require_version('Keybinder', '3.0')
|
||||
from gi.repository import Keybinder
|
||||
|
||||
Keybinder.init()
|
||||
Keybinder.set_use_cooked_accelerators(False)
|
||||
except (ImportError, ValueError) as e:
|
||||
logger.warning(e)
|
||||
logger.warning('Unable to load Keybinder module. This means the hide_window shortcut will be unavailable')
|
||||
|
||||
return
|
||||
|
||||
# Attempt to grab a global hotkey for hiding the window.
|
||||
# If we fail, we'll never hide the window, iconifying instead.
|
||||
try:
|
||||
hidebound = Keybinder.bind(self.guake_key, self._on_toggle_window, self)
|
||||
except (KeyError, NameError) as e:
|
||||
logger.warning(e)
|
||||
|
||||
if not hidebound:
|
||||
logger.debug('Unable to bind hide_window key, another instance/window has it.')
|
||||
self.hidefunc = self.iconify
|
||||
else:
|
||||
self.hidefunc = self.hide
|
||||
|
||||
def _on_toggle_window(self, data, window):
|
||||
"""Handle a request to hide/show the window"""
|
||||
if not window.get_property('visible'):
|
||||
window.show()
|
||||
# Note: Needed to properly grab widget focus when set_skip_taskbar_hint set to True
|
||||
window.present()
|
||||
# NOTE: Need here to enforce sticky after hide and reshow.
|
||||
window.stick()
|
||||
else:
|
||||
self.hidefunc()
|
||||
|
||||
|
||||
def start(self):
|
||||
Gtk.main()
|
||||
|
||||
def stop(self, widget = None, eve = None):
|
||||
event_system.emit("shutting-down")
|
||||
|
||||
size = self.get_size()
|
||||
pos = self.get_position()
|
||||
|
||||
settings_manager.set_main_window_width(size.width)
|
||||
settings_manager.set_main_window_height(size.height)
|
||||
settings_manager.set_main_window_x(pos.root_x)
|
||||
settings_manager.set_main_window_y(pos.root_y)
|
||||
settings_manager.save_settings()
|
||||
|
||||
settings_manager.clear_pid()
|
||||
Gtk.main_quit()
|
||||
3
src/libs/__init__.py
Normal file
3
src/libs/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Libs Package
|
||||
"""
|
||||
6
src/libs/db/__init__.py
Normal file
6
src/libs/db/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
"""
|
||||
DB Package
|
||||
"""
|
||||
|
||||
from .models import User
|
||||
from .db import DB
|
||||
42
src/libs/db/db.py
Normal file
42
src/libs/db/db.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# Python imports
|
||||
from typing import Optional
|
||||
from os import path
|
||||
|
||||
# Lib imports
|
||||
from sqlmodel import Session, create_engine
|
||||
|
||||
# Application imports
|
||||
from .models import SQLModel, User
|
||||
|
||||
|
||||
|
||||
class DB:
|
||||
def __init__(self):
|
||||
super(DB, self).__init__()
|
||||
|
||||
self.engine = None
|
||||
|
||||
self.create_engine()
|
||||
|
||||
|
||||
def create_engine(self):
|
||||
db_path = f"sqlite:///{settings_manager.get_home_config_path()}/database.db"
|
||||
self.engine = create_engine(db_path)
|
||||
|
||||
SQLModel.metadata.create_all(self.engine)
|
||||
|
||||
def _add_entry(self, entry):
|
||||
with Session(self.engine) as session:
|
||||
session.add(entry)
|
||||
session.commit()
|
||||
|
||||
|
||||
def add_user_entry(self, name = None, password = None, email = None):
|
||||
if not name or not password or not email: return
|
||||
|
||||
user = User()
|
||||
user.name = name
|
||||
user.password = password
|
||||
user.email = email
|
||||
|
||||
self._add_entry(user)
|
||||
15
src/libs/db/models.py
Normal file
15
src/libs/db/models.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Python imports
|
||||
from typing import Optional
|
||||
|
||||
# Lib imports
|
||||
from sqlmodel import SQLModel, Field
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class User(SQLModel, table = True):
|
||||
id: Optional[int] = Field(default = None, primary_key = True)
|
||||
name: str
|
||||
password: str
|
||||
email: Optional[str] = None
|
||||
60
src/libs/debugging.py
Normal file
60
src/libs/debugging.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
# Break into a Python console upon SIGUSR1 (Linux) or SIGBREAK (Windows:
|
||||
# CTRL+Pause/Break). To be included in all production code, just in case.
|
||||
def debug_signal_handler(signal, frame):
|
||||
del signal
|
||||
del frame
|
||||
|
||||
try:
|
||||
import rpdb2
|
||||
logger.debug("\n\nStarting embedded RPDB2 debugger. Password is 'foobar'\n\n")
|
||||
rpdb2.start_embedded_debugger("foobar", True, True)
|
||||
rpdb2.setbreak(depth=1)
|
||||
return
|
||||
except Exception:
|
||||
...
|
||||
|
||||
try:
|
||||
from rfoo.utils import rconsole
|
||||
logger.debug("\n\nStarting embedded rconsole debugger...\n\n")
|
||||
rconsole.spawn_server()
|
||||
return
|
||||
except Exception as ex:
|
||||
...
|
||||
|
||||
try:
|
||||
from pudb import set_trace
|
||||
logger.debug("\n\nStarting PuDB debugger...\n\n")
|
||||
set_trace(paused = True)
|
||||
return
|
||||
except Exception as ex:
|
||||
...
|
||||
|
||||
try:
|
||||
import ipdb
|
||||
logger.debug("\n\nStarting IPDB debugger...\n\n")
|
||||
ipdb.set_trace()
|
||||
return
|
||||
except Exception as ex:
|
||||
...
|
||||
|
||||
try:
|
||||
import pdb
|
||||
logger.debug("\n\nStarting embedded PDB debugger...\n\n")
|
||||
pdb.Pdb(skip=['gi.*']).set_trace()
|
||||
return
|
||||
except Exception as ex:
|
||||
...
|
||||
|
||||
try:
|
||||
import code
|
||||
code.interact()
|
||||
except Exception as ex:
|
||||
logger.debug(f"{ex}, returning to normal program flow...")
|
||||
5
src/libs/dto/__init__.py
Normal file
5
src/libs/dto/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
Dasta Class Package
|
||||
"""
|
||||
|
||||
from .event import Event
|
||||
18
src/libs/dto/code_event.py
Normal file
18
src/libs/dto/code_event.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .observable_event import ObservableEvent
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class CodeEvent(ObservableEvent):
|
||||
etype: str = ""
|
||||
ignore_focus: bool = False
|
||||
view: any = None
|
||||
file: any = None
|
||||
next_file: any = None
|
||||
buffer: any = None
|
||||
14
src/libs/dto/event.py
Normal file
14
src/libs/dto/event.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
@dataclass
|
||||
class Event:
|
||||
topic: str
|
||||
content: str
|
||||
raw_content: str
|
||||
10
src/libs/dto/observable_event.py
Normal file
10
src/libs/dto/observable_event.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
|
||||
|
||||
|
||||
class ObservableEvent:
|
||||
...
|
||||
22
src/libs/endpoint_registry.py
Normal file
22
src/libs/endpoint_registry.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .singleton import Singleton
|
||||
|
||||
|
||||
|
||||
class EndpointRegistry(Singleton):
|
||||
def __init__(self):
|
||||
self._endpoints = {}
|
||||
|
||||
def register(self, rule, **options):
|
||||
def decorator(f):
|
||||
self._endpoints[rule] = f
|
||||
return f
|
||||
|
||||
return decorator
|
||||
|
||||
def get_endpoints(self):
|
||||
return self._endpoints
|
||||
73
src/libs/event_system.py
Normal file
73
src/libs/event_system.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# Python imports
|
||||
from collections import defaultdict
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .singleton import Singleton
|
||||
|
||||
|
||||
|
||||
class EventSystem(Singleton):
|
||||
""" Create event system. """
|
||||
|
||||
def __init__(self):
|
||||
self.subscribers = defaultdict(list)
|
||||
self._is_paused = False
|
||||
|
||||
self._subscribe_to_events()
|
||||
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
self.subscribe("pause_event_processing", self._pause_processing_events)
|
||||
self.subscribe("resume_event_processing", self._resume_processing_events)
|
||||
|
||||
def _pause_processing_events(self):
|
||||
self._is_paused = True
|
||||
|
||||
def _resume_processing_events(self):
|
||||
self._is_paused = False
|
||||
|
||||
def subscribe(self, event_type, fn):
|
||||
self.subscribers[event_type].append(fn)
|
||||
|
||||
def unsubscribe(self, event_type, fn):
|
||||
self.subscribers[event_type].remove(fn)
|
||||
|
||||
def unsubscribe_all(self, event_type):
|
||||
self.subscribers.pop(event_type, None)
|
||||
|
||||
def emit(self, event_type, data = None):
|
||||
if self._is_paused and event_type != "resume_event_processing":
|
||||
return
|
||||
|
||||
if event_type in self.subscribers:
|
||||
for fn in self.subscribers[event_type]:
|
||||
if data:
|
||||
if hasattr(data, '__iter__') and not type(data) is str:
|
||||
fn(*data)
|
||||
else:
|
||||
fn(data)
|
||||
else:
|
||||
fn()
|
||||
|
||||
def emit_and_await(self, event_type, data = None):
|
||||
if self._is_paused and event_type != "resume_event_processing":
|
||||
return
|
||||
|
||||
""" NOTE: Should be used when signal has only one listener and vis-a-vis """
|
||||
if event_type in self.subscribers:
|
||||
response = None
|
||||
for fn in self.subscribers[event_type]:
|
||||
if data:
|
||||
if hasattr(data, '__iter__') and not type(data) is str:
|
||||
response = fn(*data)
|
||||
else:
|
||||
response = fn(data)
|
||||
else:
|
||||
response = fn()
|
||||
|
||||
if not response in (None, ''):
|
||||
break
|
||||
|
||||
return response
|
||||
148
src/libs/ipc_server.py
Normal file
148
src/libs/ipc_server.py
Normal file
@@ -0,0 +1,148 @@
|
||||
# Python imports
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from contextlib import suppress
|
||||
from multiprocessing.connection import Client
|
||||
from multiprocessing.connection import Listener
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .singleton import Singleton
|
||||
|
||||
|
||||
|
||||
class IPCServer(Singleton):
|
||||
""" Create a listener so that other {APP_NAME} instances send requests back to existing instance. """
|
||||
def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"):
|
||||
self.is_ipc_alive = False
|
||||
self._ipc_port = 0 # Use 0 to let Listener chose port
|
||||
self._ipc_address = ipc_address
|
||||
self._conn_type = conn_type
|
||||
self._ipc_authkey = b'' + bytes(f'{APP_NAME}-ipc', 'utf-8')
|
||||
self._ipc_timeout = 15.0
|
||||
|
||||
if conn_type == "socket":
|
||||
self._ipc_address = f'/tmp/{APP_NAME}-ipc.sock'
|
||||
elif conn_type == "full_network":
|
||||
self._ipc_address = '0.0.0.0'
|
||||
elif conn_type == "full_network_unsecured":
|
||||
self._ipc_authkey = None
|
||||
self._ipc_address = '0.0.0.0'
|
||||
elif conn_type == "local_network_unsecured":
|
||||
self._ipc_authkey = None
|
||||
|
||||
self._subscribe_to_events()
|
||||
|
||||
def _subscribe_to_events(self):
|
||||
event_system.subscribe("post-file-to-ipc", self.send_ipc_message)
|
||||
|
||||
|
||||
def create_ipc_listener(self) -> None:
|
||||
if self._conn_type == "socket":
|
||||
if settings_manager.is_dirty_start():
|
||||
with suppress(FileNotFoundError, PermissionError):
|
||||
os.unlink(self._ipc_address)
|
||||
|
||||
listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
|
||||
elif "unsecured" not in self._conn_type:
|
||||
listener = Listener((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey)
|
||||
else:
|
||||
listener = Listener((self._ipc_address, self._ipc_port))
|
||||
|
||||
|
||||
self.is_ipc_alive = True
|
||||
self._run_ipc_loop(listener)
|
||||
|
||||
@daemon_threaded
|
||||
def _run_ipc_loop(self, listener) -> None:
|
||||
# NOTE: Not thread safe if using with Gtk. Need to import GLib and use idle_add
|
||||
while self.is_ipc_alive:
|
||||
try:
|
||||
conn = listener.accept()
|
||||
start_time = time.perf_counter()
|
||||
self._handle_ipc_message(conn, start_time)
|
||||
except EOFError as e:
|
||||
logger.debug( repr(e) )
|
||||
except (OSError, ConnectionError, BrokenPipeError) as e:
|
||||
logger.debug( f"IPC connection error: {e}" )
|
||||
except Exception as e:
|
||||
logger.debug( f"Unexpected IPC error: {e}" )
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
listener.close()
|
||||
|
||||
def _handle_ipc_message(self, conn, start_time) -> None:
|
||||
while self.is_ipc_alive:
|
||||
msg = conn.recv()
|
||||
logger.debug(msg)
|
||||
|
||||
if "FILE|" in msg:
|
||||
file = msg.split("FILE|")[1].strip()
|
||||
if file:
|
||||
event_system.emit("handle-file-from-ipc", file)
|
||||
|
||||
conn.close()
|
||||
break
|
||||
|
||||
if "DIR|" in msg:
|
||||
file = msg.split("DIR|")[1].strip()
|
||||
if file:
|
||||
event_system.emit("handle-dir-from-ipc", file)
|
||||
|
||||
conn.close()
|
||||
break
|
||||
|
||||
|
||||
if msg in ['close connection', 'close server', 'Empty Data...']:
|
||||
conn.close()
|
||||
break
|
||||
|
||||
# NOTE: Not perfect but insures we don't lock up the connection for too long.
|
||||
end_time = time.perf_counter()
|
||||
if (end_time - start_time) > self._ipc_timeout:
|
||||
conn.close()
|
||||
break
|
||||
|
||||
|
||||
def send_ipc_message(self, message: str = "Empty Data...") -> None:
|
||||
try:
|
||||
if self._conn_type == "socket":
|
||||
conn = Client(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
|
||||
elif "unsecured" not in self._conn_type:
|
||||
conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey)
|
||||
else:
|
||||
conn = Client((self._ipc_address, self._ipc_port))
|
||||
|
||||
conn.send(message)
|
||||
conn.close()
|
||||
except ConnectionRefusedError as e:
|
||||
logger.error("Connection refused...")
|
||||
except (OSError, ConnectionError, BrokenPipeError) as e:
|
||||
logger.error( f"IPC connection error: {e}" )
|
||||
except Exception as e:
|
||||
logger.error( f"Unexpected IPC error: {e}" )
|
||||
|
||||
|
||||
def send_test_ipc_message(self, message: str = "Empty Data...") -> None:
|
||||
try:
|
||||
if self._conn_type == "socket":
|
||||
conn = Client(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
|
||||
elif "unsecured" not in self._conn_type:
|
||||
conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey)
|
||||
else:
|
||||
conn = Client((self._ipc_address, self._ipc_port))
|
||||
|
||||
conn.send(message)
|
||||
conn.close()
|
||||
except ConnectionRefusedError as e:
|
||||
if self._conn_type == "socket":
|
||||
logger.error("IPC Socket no longer valid.... Removing.")
|
||||
with suppress(FileNotFoundError, PermissionError):
|
||||
os.unlink(self._ipc_address)
|
||||
except (OSError, ConnectionError, BrokenPipeError) as e:
|
||||
logger.error( f"IPC connection error: {e}" )
|
||||
except Exception as e:
|
||||
logger.error( f"Unexpected IPC error: {e}" )
|
||||
138
src/libs/keybindings.py
Normal file
138
src/libs/keybindings.py
Normal file
@@ -0,0 +1,138 @@
|
||||
# Python imports
|
||||
import re
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gdk
|
||||
|
||||
# Application imports
|
||||
from .singleton import Singleton
|
||||
|
||||
|
||||
|
||||
def logger(log = ""):
|
||||
print(log)
|
||||
|
||||
|
||||
class KeymapError(Exception):
|
||||
""" Custom exception for errors in keybinding configurations """
|
||||
|
||||
MODIFIER = re.compile('<([^<]+)>')
|
||||
class Keybindings(Singleton):
|
||||
""" Class to handle loading and lookup of Terminator keybindings """
|
||||
|
||||
modifiers = {
|
||||
'ctrl': Gdk.ModifierType.CONTROL_MASK,
|
||||
'control': Gdk.ModifierType.CONTROL_MASK,
|
||||
'primary': Gdk.ModifierType.CONTROL_MASK,
|
||||
'shift': Gdk.ModifierType.SHIFT_MASK,
|
||||
'alt': Gdk.ModifierType.MOD1_MASK,
|
||||
'super': Gdk.ModifierType.SUPER_MASK,
|
||||
'hyper': Gdk.ModifierType.HYPER_MASK,
|
||||
'mod2': Gdk.ModifierType.MOD2_MASK
|
||||
}
|
||||
|
||||
empty = {}
|
||||
keys = None
|
||||
_masks = None
|
||||
_lookup = None
|
||||
|
||||
def __init__(self):
|
||||
self.keymap = Gdk.Keymap.get_default()
|
||||
self.configure({})
|
||||
|
||||
def print_keys(self):
|
||||
print(self.keys)
|
||||
|
||||
def append_bindings(self, combos):
|
||||
""" Accept new binding(s) and reload """
|
||||
for item in combos:
|
||||
method, keys = item.split(":")
|
||||
self.keys[method] = keys
|
||||
|
||||
self.reload()
|
||||
|
||||
def configure(self, bindings):
|
||||
""" Accept new bindings and reconfigure with them """
|
||||
self.keys = bindings
|
||||
self.reload()
|
||||
|
||||
def reload(self):
|
||||
""" Parse bindings and mangle into an appropriate form """
|
||||
self._lookup = {}
|
||||
self._masks = 0
|
||||
|
||||
for action, bindings in list(self.keys.items()):
|
||||
if isinstance(bindings, list):
|
||||
bindings = (*bindings,)
|
||||
elif not isinstance(bindings, tuple):
|
||||
bindings = (bindings,)
|
||||
|
||||
|
||||
for binding in bindings:
|
||||
if not binding or binding == "None":
|
||||
continue
|
||||
|
||||
try:
|
||||
keyval, mask = self._parsebinding(binding)
|
||||
# Does much the same, but with worse error handling.
|
||||
# keyval, mask = Gtk.accelerator_parse(binding)
|
||||
except KeymapError as e:
|
||||
logger(f"Keybinding reload failed to parse binding '{binding}': {e}")
|
||||
else:
|
||||
if mask & Gdk.ModifierType.SHIFT_MASK:
|
||||
if keyval == Gdk.KEY_Tab:
|
||||
keyval = Gdk.KEY_ISO_Left_Tab
|
||||
mask &= ~Gdk.ModifierType.SHIFT_MASK
|
||||
else:
|
||||
keyvals = Gdk.keyval_convert_case(keyval)
|
||||
if keyvals[0] != keyvals[1]:
|
||||
keyval = keyvals[1]
|
||||
mask &= ~Gdk.ModifierType.SHIFT_MASK
|
||||
else:
|
||||
keyval = Gdk.keyval_to_lower(keyval)
|
||||
|
||||
self._lookup.setdefault(mask, {})
|
||||
self._lookup[mask][keyval] = action
|
||||
self._masks |= mask
|
||||
|
||||
def _parsebinding(self, binding):
|
||||
""" Parse an individual binding using Gtk's binding function """
|
||||
mask = 0
|
||||
modifiers = re.findall(MODIFIER, binding)
|
||||
|
||||
if modifiers:
|
||||
for modifier in modifiers:
|
||||
mask |= self._lookup_modifier(modifier)
|
||||
|
||||
key = re.sub(MODIFIER, '', binding)
|
||||
if key == '':
|
||||
raise KeymapError('No key found!')
|
||||
|
||||
keyval = Gdk.keyval_from_name(key)
|
||||
|
||||
if keyval == 0:
|
||||
raise KeymapError(f"Key '{key}' is unrecognised...")
|
||||
return (keyval, mask)
|
||||
|
||||
def _lookup_modifier(self, modifier):
|
||||
""" Map modifier names to gtk values """
|
||||
try:
|
||||
return self.modifiers[modifier.lower()]
|
||||
except KeyError:
|
||||
raise KeymapError(f"Unhandled modifier '<{modifier}>'")
|
||||
|
||||
def lookup(self, event):
|
||||
""" Translate a keyboard event into a mapped key """
|
||||
try:
|
||||
_found, keyval, _egp, _lvl, consumed = self.keymap.translate_keyboard_state(
|
||||
event.hardware_keycode,
|
||||
Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK),
|
||||
event.group)
|
||||
except TypeError:
|
||||
logger(f"Keybinding lookup failed to translate keyboard event: {dir(event)}")
|
||||
return None
|
||||
|
||||
mask = (event.get_state() & ~consumed) & self._masks
|
||||
return self._lookup.get(mask, self.empty).get(keyval, None)
|
||||
61
src/libs/logger.py
Normal file
61
src/libs/logger.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# Python imports
|
||||
import os
|
||||
import logging
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from .singleton import Singleton
|
||||
|
||||
|
||||
|
||||
class Logger(Singleton):
|
||||
"""
|
||||
Create a new logging object and return it.
|
||||
:note:
|
||||
NOSET # Don't know the actual log level of this... (defaulting or literally none?)
|
||||
Log Levels (From least to most)
|
||||
Type Value
|
||||
CRITICAL 50
|
||||
ERROR 40
|
||||
WARNING 30
|
||||
INFO 20
|
||||
DEBUG 10
|
||||
:param loggerName: Sets the name of the logger object. (Used in log lines)
|
||||
:param createFile: Whether we create a log file or just pump to terminal
|
||||
|
||||
:return: the logging object we created
|
||||
"""
|
||||
|
||||
def __init__(self, config_path: str, _ch_log_lvl = logging.CRITICAL, _fh_log_lvl = logging.INFO):
|
||||
self._CONFIG_PATH = config_path
|
||||
self.global_lvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels
|
||||
self.ch_log_lvl = _ch_log_lvl # Prety much the only one we ever change
|
||||
self.fh_log_lvl = _fh_log_lvl
|
||||
|
||||
def get_logger(self, loggerName: str = "NO_LOGGER_NAME_PASSED", createFile: bool = True) -> logging.Logger:
|
||||
log = logging.getLogger(loggerName)
|
||||
log.setLevel(self.global_lvl)
|
||||
|
||||
# Set our log output styles
|
||||
fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S')
|
||||
cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s')
|
||||
|
||||
ch = logging.StreamHandler()
|
||||
ch.setLevel(level=self.ch_log_lvl)
|
||||
ch.setFormatter(cFormatter)
|
||||
log.addHandler(ch)
|
||||
|
||||
if createFile:
|
||||
folder = self._CONFIG_PATH
|
||||
file = f"{folder}/application.log"
|
||||
|
||||
if not os.path.exists(folder):
|
||||
os.mkdir(folder)
|
||||
|
||||
fh = logging.FileHandler(file)
|
||||
fh.setLevel(level=self.fh_log_lvl)
|
||||
fh.setFormatter(fFormatter)
|
||||
log.addHandler(fh)
|
||||
|
||||
return log
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user