Compare commits
36 Commits
62debf9ece
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 00c72a7117 | |||
| f8d73ad74a | |||
| 04e0c3caf6 | |||
| a57bfd94fc | |||
| c2060963cc | |||
| 8253e250d8 | |||
| be608f645e | |||
| 356da04f46 | |||
| 48182f9775 | |||
| 90b2f050c6 | |||
| 79375d34b1 | |||
| 5ee587e205 | |||
| 3fc39042f1 | |||
| e18be655e8 | |||
| 12ada8568e | |||
| 4c179974c0 | |||
| 6acbcb53c2 | |||
| ce22ed6a53 | |||
| c16493037d | |||
| 849e7611ca | |||
| 2c453bc621 | |||
| 530fe7c3ab | |||
| 5e5cc50ba8 | |||
| 22736147e6 | |||
| 4c25ce297c | |||
| 60b55da973 | |||
| 7c0d87fd20 | |||
| 4cd5a1f089 | |||
| 18dc71dcb0 | |||
| dd3e87f636 | |||
| cca007db76 | |||
| 33c0827ca2 | |||
| f2b33066af | |||
| fafc1a985f | |||
| e2e9dc8c1f | |||
| 25b6b5305b |
@@ -9,6 +9,7 @@ A template project for Python with Gtk applications.
|
|||||||
* sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy)
|
* sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy)
|
||||||
|
|
||||||
### Note
|
### Note
|
||||||
|
* pyrightconfig.json can prompt IDEs that use pyright lsp on where imports are located- look at venvPath and venv. "venvPath" is parent path of "venv" where "venv" is just the name of the folder under the parent path that is the python created venv.
|
||||||
* Move respetive sub folder content under user_config to the same places in Linux. Though, user/share/<app name> can go to ~/.config folder if prefered.
|
* Move respetive sub folder content under user_config to the same places in Linux. Though, user/share/<app name> can go to ~/.config folder if prefered.
|
||||||
* In additiion, place the plugins folder in the same app folder you moved to /usr/share/<app name> or ~/.config/<app name> .
|
* In additiion, place the plugins folder in the same app folder you moved to /usr/share/<app name> or ~/.config/<app name> .
|
||||||
There are a "\<change_me\>" strings and files that need to be set according to your app's name located at:
|
There are a "\<change_me\>" strings and files that need to be set according to your app's name located at:
|
||||||
|
|||||||
@@ -10,19 +10,19 @@ class Manifest:
|
|||||||
author: str = "John Doe"
|
author: str = "John Doe"
|
||||||
version: str = "0.0.1"
|
version: str = "0.0.1"
|
||||||
support: str = ""
|
support: str = ""
|
||||||
|
pre_launch: bool = False
|
||||||
requests: {} = {
|
requests: {} = {
|
||||||
'pass_ui_objects': ["plugin_control_list"],
|
'pass_ui_objects': ["plugin_control_list"],
|
||||||
'pass_events': "true",
|
'pass_events': True,
|
||||||
'bind_keys': []
|
'bind_keys': []
|
||||||
}
|
}
|
||||||
pre_launch: bool = False
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Requests
|
### Requests
|
||||||
```
|
```
|
||||||
requests: {} = {
|
requests: {} = {
|
||||||
'pass_events': "true", # If empty or not present will be ignored.
|
'pass_events': true, # If empty or not present will be ignored.
|
||||||
"pass_ui_objects": [""], # Request reference to a UI component. Will be passed back as array to plugin.
|
"pass_ui_objects": [""], # Request reference to a UI component. Will be passed back as array to plugin.
|
||||||
'bind_keys': [f"{name}||send_message:<Control>f"],
|
'bind_keys': [f"{name}||send_message:<Control>f"],
|
||||||
f"{name}||do_save:<Control>s"] # Bind keys with method and key pare using list. Must pass "name" like shown with delimiter to its right.
|
f"{name}||do_save:<Control>s"] # Bind keys with method and key pare using list. Must pass "name" like shown with delimiter to its right.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"support": "",
|
"support": "",
|
||||||
"requests": {
|
"requests": {
|
||||||
"ui_target": "plugin_control_list",
|
"ui_target": "plugin_control_list",
|
||||||
"pass_events": "true",
|
"pass_events": true,
|
||||||
"bind_keys": ["Example Plugin||send_message:<Control>f"]
|
"bind_keys": ["Example Plugin||send_message:<Control>f"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
pyrightconfig.json
Normal file
13
pyrightconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"reportUndefinedVariable": false,
|
||||||
|
"reportUnusedVariable": false,
|
||||||
|
"reportUnusedImport": true,
|
||||||
|
"reportDuplicateImport": true,
|
||||||
|
"executionEnvironments": [
|
||||||
|
{
|
||||||
|
"root": "./src/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"venvPath": ".",
|
||||||
|
"venv": ".venv"
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
import builtins
|
import builtins
|
||||||
|
import traceback
|
||||||
import threading
|
import threading
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@@ -31,6 +32,17 @@ def daemon_threaded_wrapper(fn):
|
|||||||
return thread
|
return thread
|
||||||
return wrapper
|
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...
|
# NOTE: Just reminding myself we can add to builtins two different ways...
|
||||||
@@ -45,13 +57,15 @@ builtins.settings_manager = SettingsManager()
|
|||||||
|
|
||||||
settings_manager.load_settings()
|
settings_manager.load_settings()
|
||||||
|
|
||||||
builtins.settings = settings_manager.settings
|
builtins.logger = Logger(
|
||||||
builtins.logger = Logger(settings_manager.get_home_config_path(), \
|
settings_manager.path_manager.get_home_config_path(), \
|
||||||
_ch_log_lvl=settings.debugging.ch_log_lvl, \
|
_ch_log_lvl = settings_manager.settings.debugging.ch_log_lvl, \
|
||||||
_fh_log_lvl=settings.debugging.fh_log_lvl).get_logger()
|
_fh_log_lvl = settings_manager.settings.debugging.fh_log_lvl
|
||||||
|
).get_logger()
|
||||||
|
|
||||||
builtins.threaded = threaded_wrapper
|
builtins.threaded = threaded_wrapper
|
||||||
builtins.daemon_threaded = daemon_threaded_wrapper
|
builtins.daemon_threaded = daemon_threaded_wrapper
|
||||||
|
builtins.call_chain = call_chain_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""
|
"""
|
||||||
Start of package.
|
Src Package.
|
||||||
"""
|
"""
|
||||||
@@ -17,8 +17,9 @@ from app import Application
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def main(args, unknownargs):
|
def main():
|
||||||
setproctitle(f'{APP_NAME}')
|
setproctitle(f'{APP_NAME}')
|
||||||
|
settings_manager.set_start_load_time()
|
||||||
|
|
||||||
if args.debug == "true":
|
if args.debug == "true":
|
||||||
settings_manager.set_debug(True)
|
settings_manager.set_debug(True)
|
||||||
@@ -27,7 +28,9 @@ def main(args, unknownargs):
|
|||||||
settings_manager.set_trace_debug(True)
|
settings_manager.set_trace_debug(True)
|
||||||
|
|
||||||
settings_manager.do_dirty_start_check()
|
settings_manager.do_dirty_start_check()
|
||||||
Application(args, unknownargs)
|
|
||||||
|
app = Application()
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -45,10 +48,11 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Read arguments (If any...)
|
# Read arguments (If any...)
|
||||||
args, unknownargs = parser.parse_known_args()
|
args, unknownargs = parser.parse_known_args()
|
||||||
|
settings_manager.set_starting_args( args, unknownargs )
|
||||||
|
|
||||||
try:
|
try:
|
||||||
faulthandler.enable() # For better debug info
|
faulthandler.enable() # For better debug info
|
||||||
main(args, unknownargs)
|
main()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
quit()
|
quit()
|
||||||
50
src/app.py
50
src/app.py
@@ -1,4 +1,5 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
|
from contextlib import suppress
|
||||||
import signal
|
import signal
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@@ -19,46 +20,57 @@ class AppLaunchException(Exception):
|
|||||||
class Application:
|
class Application:
|
||||||
""" docstring for Application. """
|
""" docstring for Application. """
|
||||||
|
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self):
|
||||||
super(Application, self).__init__()
|
super(Application, self).__init__()
|
||||||
|
|
||||||
if not settings_manager.is_trace_debug():
|
|
||||||
self.load_ipc(args, unknownargs)
|
|
||||||
|
|
||||||
self.setup_debug_hook()
|
self.setup_debug_hook()
|
||||||
Window(args, unknownargs).main()
|
|
||||||
|
|
||||||
|
|
||||||
def load_ipc(self, args, unknownargs):
|
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()
|
ipc_server = IPCServer()
|
||||||
self.ipc_realization_check(ipc_server)
|
|
||||||
|
|
||||||
if not ipc_server.is_ipc_alive:
|
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,]:
|
for arg in unknownargs + [args.new_tab,]:
|
||||||
if os.path.isfile(arg):
|
if os.path.isfile(arg):
|
||||||
message = f"FILE|{arg}"
|
message = f"FILE|{arg}"
|
||||||
ipc_server.send_ipc_message(message)
|
ipc_server.send_ipc_message(message)
|
||||||
|
|
||||||
raise AppLaunchException(f"{APP_NAME} IPC Server Exists: Have sent path(s) to it and closing...")
|
if os.path.isdir(arg):
|
||||||
|
message = f"DIR|{arg}"
|
||||||
|
ipc_server.send_ipc_message(message)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def ipc_realization_check(self, ipc_server):
|
def ipc_realization_check(self, ipc_server):
|
||||||
try:
|
try:
|
||||||
ipc_server.create_ipc_listener()
|
ipc_server.create_ipc_listener()
|
||||||
except Exception:
|
except (OSError, PermissionError) as e:
|
||||||
|
logger.info(f"IPC listener creation failed: {e}, falling back to test message")
|
||||||
|
ipc_server.send_test_ipc_message()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Unexpected IPC setup error: {e}")
|
||||||
ipc_server.send_test_ipc_message()
|
ipc_server.send_test_ipc_message()
|
||||||
|
|
||||||
try:
|
|
||||||
ipc_server.create_ipc_listener()
|
|
||||||
except Exception as e:
|
|
||||||
...
|
|
||||||
|
|
||||||
def setup_debug_hook(self):
|
def setup_debug_hook(self):
|
||||||
try:
|
# Typically: ValueError: signal only works in main thread
|
||||||
|
with suppress(ValueError):
|
||||||
# kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows
|
# kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows
|
||||||
signal.signal(
|
signal.signal(
|
||||||
vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR2"),
|
vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR2"),
|
||||||
debug_signal_handler
|
debug_signal_handler
|
||||||
)
|
)
|
||||||
except ValueError:
|
|
||||||
# Typically: ValueError: signal only works in main thread
|
|
||||||
...
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""
|
"""
|
||||||
Core Module
|
Core Package
|
||||||
"""
|
"""
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
"""
|
"""
|
||||||
Containers Module
|
Containers Package
|
||||||
"""
|
"""
|
||||||
@@ -8,6 +8,7 @@ from gi.repository import Gtk
|
|||||||
# Application imports
|
# Application imports
|
||||||
from .header_container import HeaderContainer
|
from .header_container import HeaderContainer
|
||||||
from .body_container import BodyContainer
|
from .body_container import BodyContainer
|
||||||
|
from .footer_container import FooterContainer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -15,20 +16,20 @@ class BaseContainer(Gtk.Box):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(BaseContainer, self).__init__()
|
super(BaseContainer, self).__init__()
|
||||||
|
|
||||||
self.ctx = self.get_style_context()
|
|
||||||
|
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
self._load_widgets()
|
self._load_widgets()
|
||||||
|
|
||||||
self.show_all()
|
self.show()
|
||||||
|
|
||||||
|
|
||||||
def _setup_styling(self):
|
def _setup_styling(self):
|
||||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
self.ctx = self.get_style_context()
|
||||||
self.ctx.add_class("base-container")
|
self.ctx.add_class("base-container")
|
||||||
|
|
||||||
|
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -39,9 +40,10 @@ class BaseContainer(Gtk.Box):
|
|||||||
def _load_widgets(self):
|
def _load_widgets(self):
|
||||||
self.add( HeaderContainer() )
|
self.add( HeaderContainer() )
|
||||||
self.add( BodyContainer() )
|
self.add( BodyContainer() )
|
||||||
|
self.add( FooterContainer() )
|
||||||
|
|
||||||
def _update_transparency(self):
|
def _update_transparency(self):
|
||||||
self.ctx.add_class(f"mw_transparency_{settings.theming.transparency}")
|
self.ctx.add_class(f"mw_transparency_{settings_manager.settings.theming.transparency}")
|
||||||
|
|
||||||
def _remove_transparency(self):
|
def _remove_transparency(self):
|
||||||
self.ctx.remove_class(f"mw_transparency_{settings.theming.transparency}")
|
self.ctx.remove_class(f"mw_transparency_{settings_manager.settings.theming.transparency}")
|
||||||
@@ -16,19 +16,19 @@ class BodyContainer(Gtk.Box):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(BodyContainer, self).__init__()
|
super(BodyContainer, self).__init__()
|
||||||
|
|
||||||
self.ctx = self.get_style_context()
|
|
||||||
|
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
self._load_widgets()
|
self._load_widgets()
|
||||||
|
|
||||||
self.show_all()
|
self.show()
|
||||||
|
|
||||||
|
|
||||||
def _setup_styling(self):
|
def _setup_styling(self):
|
||||||
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
self.ctx = self.get_style_context()
|
||||||
self.ctx.add_class("body-container")
|
self.ctx.add_class("body-container")
|
||||||
|
|
||||||
|
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||||
self.set_homogeneous(True)
|
self.set_homogeneous(True)
|
||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
@@ -37,7 +37,6 @@ class BodyContainer(Gtk.Box):
|
|||||||
def _subscribe_to_events(self):
|
def _subscribe_to_events(self):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
def _load_widgets(self):
|
def _load_widgets(self):
|
||||||
self.add( LeftContainer() )
|
self.add( LeftContainer() )
|
||||||
self.add( CenterContainer() )
|
self.add( CenterContainer() )
|
||||||
|
|||||||
@@ -21,11 +21,16 @@ class CenterContainer(Gtk.Box):
|
|||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
self._load_widgets()
|
self._load_widgets()
|
||||||
|
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
|
||||||
def _setup_styling(self):
|
def _setup_styling(self):
|
||||||
|
self.ctx = self.get_style_context()
|
||||||
|
self.ctx.add_class("center-container")
|
||||||
|
|
||||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||||
ctx = self.get_style_context()
|
self.set_hexpand(True)
|
||||||
ctx.add_class("center-container")
|
self.set_vexpand(True)
|
||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
...
|
...
|
||||||
@@ -39,6 +44,9 @@ class CenterContainer(Gtk.Box):
|
|||||||
|
|
||||||
button.connect("clicked", self._hello_world)
|
button.connect("clicked", self._hello_world)
|
||||||
|
|
||||||
|
button.show()
|
||||||
|
glade_box.show()
|
||||||
|
|
||||||
self.add(button)
|
self.add(button)
|
||||||
self.add(glade_box)
|
self.add(glade_box)
|
||||||
self.add( WebkitUI() )
|
self.add( WebkitUI() )
|
||||||
|
|||||||
3
src/core/containers/code/__init__.py
Normal file
3
src/core/containers/code/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Containers Package
|
||||||
|
"""
|
||||||
64
src/core/containers/code/code_container.py
Normal file
64
src/core/containers/code/code_container.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from ...widgets.code.code_base import CodeBase
|
||||||
|
|
||||||
|
from ...widgets.separator_widget import Separator
|
||||||
|
from ...widgets.code.mini_view_widget import MiniViewWidget
|
||||||
|
|
||||||
|
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):
|
||||||
|
code_base = CodeBase()
|
||||||
|
|
||||||
|
self.add( self._create_tabs_widgets(code_base) )
|
||||||
|
self.add( self._create_editor_widget(code_base) )
|
||||||
|
|
||||||
|
def _create_tabs_widgets(self, code_base: CodeBase):
|
||||||
|
scrolled_window = Gtk.ScrolledWindow()
|
||||||
|
viewport = Gtk.Viewport()
|
||||||
|
|
||||||
|
scrolled_window.set_overlay_scrolling(False)
|
||||||
|
|
||||||
|
viewport.add( code_base.get_tabs_widget() )
|
||||||
|
scrolled_window.add( viewport )
|
||||||
|
|
||||||
|
return scrolled_window
|
||||||
|
|
||||||
|
def _create_editor_widget(self, code_base: CodeBase):
|
||||||
|
editors_container = Gtk.Box()
|
||||||
|
|
||||||
|
editors_container.add( Separator("separator_left") )
|
||||||
|
editors_container.add( EditorsContainer(code_base) )
|
||||||
|
editors_container.add( Separator("separator_right") )
|
||||||
|
editors_container.add( code_base.get_mini_view_widget() )
|
||||||
|
|
||||||
|
return editors_container
|
||||||
71
src/core/containers/code/editors_container.py
Normal file
71
src/core/containers/code/editors_container.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class EditorsContainer(Gtk.Paned):
|
||||||
|
def __init__(self, code_base: any):
|
||||||
|
super(EditorsContainer, self).__init__()
|
||||||
|
|
||||||
|
self.code_base = code_base
|
||||||
|
|
||||||
|
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_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):
|
||||||
|
self.scrolled_win1, \
|
||||||
|
self.scrolled_win2 = self._create_views()
|
||||||
|
|
||||||
|
self.pack1( self.scrolled_win1, True, True )
|
||||||
|
self.pack2( self.scrolled_win2, True, True )
|
||||||
|
|
||||||
|
def _create_views(self):
|
||||||
|
scrolled_win1 = Gtk.ScrolledWindow()
|
||||||
|
scrolled_win2 = Gtk.ScrolledWindow()
|
||||||
|
|
||||||
|
source_view1 = self.code_base.create_source_view()
|
||||||
|
source_view2 = self.code_base.create_source_view()
|
||||||
|
|
||||||
|
source_view1.sibling_right = source_view2
|
||||||
|
source_view2.sibling_left = source_view1
|
||||||
|
|
||||||
|
scrolled_win1.add( source_view1 )
|
||||||
|
scrolled_win2.add( source_view2 )
|
||||||
|
|
||||||
|
return scrolled_win1, scrolled_win2
|
||||||
|
|
||||||
|
def _init_map(self, view):
|
||||||
|
def _first_show_init():
|
||||||
|
self.disconnect(self.map_id)
|
||||||
|
del self.map_id
|
||||||
|
|
||||||
|
self.code_base.first_map_load()
|
||||||
|
|
||||||
|
del self.code_base
|
||||||
|
return False
|
||||||
|
|
||||||
|
GLib.timeout_add(100, _first_show_init)
|
||||||
39
src/core/containers/footer_container.py
Normal file
39
src/core/containers/footer_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 .code.code_container import CodeContainer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.add( CodeContainer() )
|
||||||
@@ -15,27 +15,27 @@ class HeaderContainer(Gtk.Box):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(HeaderContainer, self).__init__()
|
super(HeaderContainer, self).__init__()
|
||||||
|
|
||||||
self.ctx = self.get_style_context()
|
|
||||||
|
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
self._load_widgets()
|
self._load_widgets()
|
||||||
|
|
||||||
self.show_all()
|
self.show()
|
||||||
|
|
||||||
|
|
||||||
def _setup_styling(self):
|
def _setup_styling(self):
|
||||||
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
self.ctx = self.get_style_context()
|
||||||
self.ctx.add_class("header-container")
|
self.ctx.add_class("header-container")
|
||||||
|
|
||||||
|
self.set_orientation(Gtk.Orientation.HORIZONTAL)
|
||||||
|
self.set_hexpand(True)
|
||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
...
|
...
|
||||||
|
|
||||||
def _subscribe_to_events(self):
|
def _subscribe_to_events(self):
|
||||||
event_system.subscribe("tggl-top-main-menubar", self.tggl_top_main_menubar)
|
event_system.subscribe("tggl-top-main-menubar", self.tggl_top_main_menubar)
|
||||||
|
|
||||||
|
|
||||||
def _load_widgets(self):
|
def _load_widgets(self):
|
||||||
button = Gtk.Button(label = "Interactive Debug")
|
button = Gtk.Button(label = "Interactive Debug")
|
||||||
button.connect("clicked", self._interactive_debug)
|
button.connect("clicked", self._interactive_debug)
|
||||||
@@ -48,4 +48,4 @@ class HeaderContainer(Gtk.Box):
|
|||||||
event_system.emit("load-interactive-debug")
|
event_system.emit("load-interactive-debug")
|
||||||
|
|
||||||
def tggl_top_main_menubar(self):
|
def tggl_top_main_menubar(self):
|
||||||
self.hide() if self.is_visible() else self.show()
|
self.hide() if self.is_visible() else self.show_all()
|
||||||
@@ -18,11 +18,15 @@ class LeftContainer(Gtk.Box):
|
|||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
self._load_widgets()
|
self._load_widgets()
|
||||||
|
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
|
||||||
def _setup_styling(self):
|
def _setup_styling(self):
|
||||||
|
self.ctx = self.get_style_context()
|
||||||
|
self.ctx.add_class("left-container")
|
||||||
|
|
||||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||||
ctx = self.get_style_context()
|
self.set_vexpand(True)
|
||||||
ctx.add_class("left-container")
|
|
||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ gi.require_version('Gtk', '3.0')
|
|||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
from ..widgets.vte_widget import VteWidget
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -18,11 +19,15 @@ class RightContainer(Gtk.Box):
|
|||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
self._load_widgets()
|
self._load_widgets()
|
||||||
|
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
|
||||||
def _setup_styling(self):
|
def _setup_styling(self):
|
||||||
|
self.ctx = self.get_style_context()
|
||||||
|
self.ctx.add_class("right-container")
|
||||||
|
|
||||||
self.set_orientation(Gtk.Orientation.VERTICAL)
|
self.set_orientation(Gtk.Orientation.VERTICAL)
|
||||||
ctx = self.get_style_context()
|
self.set_vexpand(True)
|
||||||
ctx.add_class("right-container")
|
|
||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
...
|
...
|
||||||
@@ -31,4 +36,5 @@ class RightContainer(Gtk.Box):
|
|||||||
...
|
...
|
||||||
|
|
||||||
def _load_widgets(self):
|
def _load_widgets(self):
|
||||||
...
|
vte_widget = VteWidget()
|
||||||
|
self.add( vte_widget )
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
"""
|
"""
|
||||||
Controllers Module
|
Controllers Package
|
||||||
"""
|
"""
|
||||||
@@ -17,26 +17,20 @@ from .bridge_controller import BridgeController
|
|||||||
|
|
||||||
|
|
||||||
class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
|
class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
|
||||||
def __init__(self, args, unknownargs):
|
""" docstring for BaseController. """
|
||||||
self.collect_files_dirs(args, unknownargs)
|
|
||||||
|
|
||||||
self.setup_controller_data()
|
def __init__(self):
|
||||||
|
|
||||||
|
self._setup_controller_data()
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
self._load_controllers()
|
self._load_controllers()
|
||||||
|
self._load_plugins_and_files()
|
||||||
if args.no_plugins == "false":
|
|
||||||
self.plugins_controller.pre_launch_plugins()
|
|
||||||
|
|
||||||
if args.no_plugins == "false":
|
|
||||||
self.plugins_controller.post_launch_plugins()
|
|
||||||
|
|
||||||
for file in settings_manager.get_starting_files():
|
|
||||||
event_system.emit("post-file-to-ipc", file)
|
|
||||||
|
|
||||||
logger.info(f"Made it past {self.__class__} loading...")
|
logger.info(f"Made it past {self.__class__} loading...")
|
||||||
|
settings_manager.set_end_load_time()
|
||||||
|
settings_manager.log_load_time()
|
||||||
|
|
||||||
|
|
||||||
def _setup_styling(self):
|
def _setup_styling(self):
|
||||||
@@ -56,15 +50,23 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
|
|||||||
def _load_controllers(self):
|
def _load_controllers(self):
|
||||||
BridgeController()
|
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()
|
||||||
|
|
||||||
|
for file in settings_manager.get_starting_files():
|
||||||
|
event_system.emit("post-file-to-ipc", file)
|
||||||
|
|
||||||
def _tggl_top_main_menubar(self):
|
def _tggl_top_main_menubar(self):
|
||||||
logger.debug("_tggl_top_main_menubar > stub...")
|
logger.debug("_tggl_top_main_menubar > stub...")
|
||||||
|
|
||||||
def _load_glade_file(self):
|
def _load_glade_file(self):
|
||||||
self.builder.add_from_file(settings_manager.get_glade_file())
|
self.builder.add_from_file( settings_manager.path_manager.get_glade_file() )
|
||||||
self.builder.expose_object("main_window", self.window)
|
self.builder.expose_object("main_window", self.window)
|
||||||
|
|
||||||
settings_manager.set_builder(self.builder)
|
settings_manager.set_builder(self.builder)
|
||||||
self.base_container = BaseContainer()
|
self.base_container = BaseContainer()
|
||||||
|
|
||||||
settings_manager.register_signals_to_builder([self, self.base_container])
|
settings_manager.register_signals_to_builder([self, self.base_container])
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from ..builder_wrapper import BuilderWrapper
|
|||||||
class BaseControllerData:
|
class BaseControllerData:
|
||||||
''' BaseControllerData contains most of the state of the app at ay given time. It also has some support methods. '''
|
''' 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:
|
def _setup_controller_data(self) -> None:
|
||||||
self.window = settings_manager.get_main_window()
|
self.window = settings_manager.get_main_window()
|
||||||
self.builder = BuilderWrapper()
|
self.builder = BuilderWrapper()
|
||||||
self.plugins_controller = PluginsController()
|
self.plugins_controller = PluginsController()
|
||||||
@@ -25,20 +25,29 @@ class BaseControllerData:
|
|||||||
self.shift_down = False
|
self.shift_down = False
|
||||||
self.alt_down = False
|
self.alt_down = False
|
||||||
|
|
||||||
|
self._collect_files_dirs()
|
||||||
self._load_glade_file()
|
self._load_glade_file()
|
||||||
|
|
||||||
|
|
||||||
def collect_files_dirs(self, args, unknownargs):
|
def _collect_files_dirs(self):
|
||||||
|
args, \
|
||||||
|
unknownargs = settings_manager.get_starting_args()
|
||||||
files = []
|
files = []
|
||||||
|
|
||||||
for arg in unknownargs + [args.new_tab,]:
|
for arg in unknownargs + [args.new_tab,]:
|
||||||
if os.path.isdir( arg.replace("file://", "") ):
|
if os.path.isdir( arg.replace("file://", "") ):
|
||||||
files.append( f"DIR|{arg.replace('file://', '')}" )
|
files.append( f"DIR|{arg.replace('file://', '')}" )
|
||||||
|
continue
|
||||||
|
|
||||||
# NOTE: If passing line number with file split against :
|
# NOTE: If passing line number with file split against :
|
||||||
if os.path.isfile( arg.replace("file://", "").split(":")[0] ):
|
if os.path.isfile( arg.replace("file://", "").split(":")[0] ):
|
||||||
files.append( f"FILE|{arg.replace('file://', '')}" )
|
files.append( f"FILE|{arg.replace('file://', '')}" )
|
||||||
|
continue
|
||||||
|
|
||||||
|
logger.info(f"Not a File: {arg}")
|
||||||
|
|
||||||
|
if len(files) == 0: return
|
||||||
|
|
||||||
if len(files) > 0:
|
|
||||||
settings_manager.set_is_starting_with_file(True)
|
settings_manager.set_is_starting_with_file(True)
|
||||||
settings_manager.set_starting_files(files)
|
settings_manager.set_starting_files(files)
|
||||||
|
|
||||||
@@ -76,24 +85,22 @@ class BaseControllerData:
|
|||||||
widget.remove(child)
|
widget.remove(child)
|
||||||
|
|
||||||
def get_clipboard_data(self, encoding = "utf-8") -> str:
|
def get_clipboard_data(self, encoding = "utf-8") -> str:
|
||||||
if which("xclip"):
|
if not which("xclip"):
|
||||||
command = ['xclip','-selection','clipboard']
|
|
||||||
else:
|
|
||||||
logger.info('xclip not found...')
|
logger.info('xclip not found...')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
command = ['xclip','-selection','clipboard']
|
||||||
proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout = subprocess.PIPE)
|
proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout = subprocess.PIPE)
|
||||||
retcode = proc.wait()
|
retcode = proc.wait()
|
||||||
data = proc.stdout.read()
|
data = proc.stdout.read()
|
||||||
return data.decode(encoding).strip()
|
return data.decode(encoding).strip()
|
||||||
|
|
||||||
def set_clipboard_data(self, data: type, encoding = "utf-8") -> None:
|
def set_clipboard_data(self, data: type, encoding = "utf-8") -> None:
|
||||||
if which("xclip"):
|
if not which("xclip"):
|
||||||
command = ['xclip','-selection','clipboard']
|
|
||||||
else:
|
|
||||||
logger.info('xclip not found...')
|
logger.info('xclip not found...')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
command = ['xclip','-selection','clipboard']
|
||||||
proc = subprocess.Popen(command, stdin = subprocess.PIPE)
|
proc = subprocess.Popen(command, stdin = subprocess.PIPE)
|
||||||
proc.stdin.write(data.encode(encoding))
|
proc.stdin.write(data.encode(encoding))
|
||||||
proc.stdin.close()
|
proc.stdin.close()
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import base64
|
|||||||
class BridgeController:
|
class BridgeController:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.opened_files = {}
|
|
||||||
|
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""
|
"""
|
||||||
Widgets Module
|
Widgets Package
|
||||||
"""
|
"""
|
||||||
110
src/core/widgets/clock_widget.py
Normal file
110
src/core/widgets/clock_widget.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# Python imports
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from gi.repository import GObject
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarWidget(Gtk.Popover):
|
||||||
|
def __init__(self):
|
||||||
|
super(CalendarWidget, 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.body = Gtk.Calendar()
|
||||||
|
|
||||||
|
self.body.show()
|
||||||
|
self.add(self.body)
|
||||||
|
|
||||||
|
|
||||||
|
class ClockWidget(Gtk.EventBox):
|
||||||
|
def __init__(self):
|
||||||
|
super(ClockWidget, self).__init__()
|
||||||
|
|
||||||
|
self._setup_styling()
|
||||||
|
self._setup_signals()
|
||||||
|
self._subscribe_to_events()
|
||||||
|
self._load_widgets()
|
||||||
|
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_styling(self):
|
||||||
|
self.set_size_request(180, -1)
|
||||||
|
|
||||||
|
ctx = self.get_style_context()
|
||||||
|
ctx.add_class("clock-widget")
|
||||||
|
|
||||||
|
def _setup_signals(self):
|
||||||
|
self.connect("button_release_event", self._toggle_cal_popover)
|
||||||
|
|
||||||
|
|
||||||
|
def _subscribe_to_events(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _load_widgets(self):
|
||||||
|
self.calendar = CalendarWidget()
|
||||||
|
self.label = Gtk.Label()
|
||||||
|
|
||||||
|
self.calendar.set_relative_to(self)
|
||||||
|
|
||||||
|
self.label.set_justify(Gtk.Justification.CENTER)
|
||||||
|
self.label.set_margin_top(15)
|
||||||
|
self.label.set_margin_bottom(15)
|
||||||
|
self.label.set_margin_left(15)
|
||||||
|
self.label.set_margin_right(15)
|
||||||
|
|
||||||
|
self._update_face()
|
||||||
|
self.add(self.label)
|
||||||
|
|
||||||
|
GObject.timeout_add(59000, self._update_face)
|
||||||
|
|
||||||
|
def _update_face(self):
|
||||||
|
dt_now = datetime.now()
|
||||||
|
hours_mins_sec = dt_now.strftime("%I:%M %p")
|
||||||
|
month_day_year = dt_now.strftime("%m/%d/%Y")
|
||||||
|
time_str = hours_mins_sec + "\n" + month_day_year
|
||||||
|
|
||||||
|
self.label.set_label(time_str)
|
||||||
|
|
||||||
|
def _toggle_cal_popover(self, widget, eve):
|
||||||
|
if (self.calendar.get_visible() == True):
|
||||||
|
self.calendar.popdown()
|
||||||
|
return
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
timeStr = now.strftime("%m/%d/%Y")
|
||||||
|
parts = timeStr.split("/")
|
||||||
|
month = int(parts[0]) - 1
|
||||||
|
day = int(parts[1])
|
||||||
|
year = int(parts[2])
|
||||||
|
|
||||||
|
self.calendar.body.select_day(day)
|
||||||
|
self.calendar.body.select_month(month, year)
|
||||||
|
|
||||||
|
self.calendar.popup()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
3
src/core/widgets/code/__init__.py
Normal file
3
src/core/widgets/code/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Code Package
|
||||||
|
"""
|
||||||
50
src/core/widgets/code/code_base.py
Normal file
50
src/core/widgets/code/code_base.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from .controllers.controller_manager import ControllerManager
|
||||||
|
from .controllers.files_controller import FilesController
|
||||||
|
from .controllers.tabs_controller import TabsController
|
||||||
|
from .controllers.commands_controller import CommandsController
|
||||||
|
from .controllers.completion_controller import CompletionController
|
||||||
|
from .controllers.source_views_controller import SourceViewsController
|
||||||
|
|
||||||
|
from .mini_view_widget import MiniViewWidget
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CodeBase:
|
||||||
|
def __init__(self):
|
||||||
|
super(CodeBase, self).__init__()
|
||||||
|
|
||||||
|
self.controller_manager: ControllerManager = ControllerManager()
|
||||||
|
self.miniview_widget: MiniViewWidget = MiniViewWidget()
|
||||||
|
|
||||||
|
self._load_controllers()
|
||||||
|
|
||||||
|
|
||||||
|
def _load_controllers(self):
|
||||||
|
files_controller = FilesController()
|
||||||
|
tabs_controller = TabsController()
|
||||||
|
commands_controller = CommandsController()
|
||||||
|
completion_controller = CompletionController()
|
||||||
|
source_views_controller = SourceViewsController()
|
||||||
|
|
||||||
|
self.controller_manager.register_controller("files", files_controller)
|
||||||
|
self.controller_manager.register_controller("tabs", tabs_controller)
|
||||||
|
self.controller_manager.register_controller("commands", commands_controller)
|
||||||
|
self.controller_manager.register_controller("completion", completion_controller)
|
||||||
|
self.controller_manager.register_controller("source_views", source_views_controller)
|
||||||
|
|
||||||
|
def get_tabs_widget(self):
|
||||||
|
return self.controller_manager["tabs"].get_tabs_widget()
|
||||||
|
|
||||||
|
def get_mini_view_widget(self):
|
||||||
|
return self.miniview_widget
|
||||||
|
|
||||||
|
def create_source_view(self):
|
||||||
|
return self.controller_manager["source_views"].create_source_view()
|
||||||
|
|
||||||
|
def first_map_load(self):
|
||||||
|
self.controller_manager["source_views"].first_map_load()
|
||||||
27
src/core/widgets/code/command_helpers.py
Normal file
27
src/core/widgets/code/command_helpers.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
from gi.repository import GtkSource
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def set_language_and_style(view, file):
|
||||||
|
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)
|
||||||
|
|
||||||
|
return language
|
||||||
|
|
||||||
|
def update_info_bar_if_focused(command_system, view: GtkSource):
|
||||||
|
has_focus = command_system.exec("has_focus")
|
||||||
|
if has_focus:
|
||||||
|
command_system.exec("update_info_bar")
|
||||||
|
|
||||||
|
def get_file_and_buffer(view: GtkSource):
|
||||||
|
file = view.command.get_file(view)
|
||||||
|
buffer = file.buffer
|
||||||
|
|
||||||
|
return file, buffer
|
||||||
95
src/core/widgets/code/command_system.py
Normal file
95
src/core/widgets/code/command_system.py
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.code import CodeEvent
|
||||||
|
from .event_factory import Event_Factory, Event_Factory_Types
|
||||||
|
|
||||||
|
from . import commands
|
||||||
|
|
||||||
|
from .source_view import SourceView
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def emit(self, event: CodeEvent):
|
||||||
|
""" Monky patch 'emit' from command controller... """
|
||||||
|
...
|
||||||
|
|
||||||
|
def emit_to(self, controller: str, event: CodeEvent):
|
||||||
|
""" Monky patch 'emit' from command controller... """
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def get_file(self, view: SourceView):
|
||||||
|
event = Event_Factory.create_get_file(
|
||||||
|
view = view,
|
||||||
|
buffer = view.get_buffer()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.emit_to("files", event)
|
||||||
|
|
||||||
|
return event.response
|
||||||
|
|
||||||
|
def get_swap_file(self, view: SourceView):
|
||||||
|
event = Event_Factory.create_get_swap_file(
|
||||||
|
view = view,
|
||||||
|
buffer = view.get_buffer()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.emit_to("files", event)
|
||||||
|
|
||||||
|
return event.response
|
||||||
|
|
||||||
|
def new_file(self, view: SourceView):
|
||||||
|
event = Event_Factory.create_event("add_new_file", view = view)
|
||||||
|
|
||||||
|
self.emit_to("files", event)
|
||||||
|
|
||||||
|
return event.response
|
||||||
|
|
||||||
|
def remove_file(self, view: SourceView):
|
||||||
|
event = Event_Factory.create_remove_file(
|
||||||
|
view = view,
|
||||||
|
buffer = view.get_buffer()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.emit_to("files", event)
|
||||||
|
|
||||||
|
return event.response
|
||||||
|
|
||||||
|
def request_completion(self, view: SourceView):
|
||||||
|
event = Event_Factory.create_event(
|
||||||
|
"request_completion",
|
||||||
|
view = view,
|
||||||
|
buffer = view.get_buffer()
|
||||||
|
)
|
||||||
|
|
||||||
|
self.emit_to("completion", event)
|
||||||
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("Command: Buffer Redo")
|
||||||
|
|
||||||
|
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("Command: Buffer Undo")
|
||||||
|
|
||||||
|
buffer = view.get_buffer()
|
||||||
|
undo_manager = buffer.get_undo_manager()
|
||||||
|
|
||||||
|
if undo_manager.can_undo():
|
||||||
|
buffer.undo()
|
||||||
20
src/core/widgets/code/commands/close_file.py
Normal file
20
src/core/widgets/code/commands/close_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
|
||||||
|
from ..command_helpers import update_info_bar_if_focused
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View = None
|
||||||
|
):
|
||||||
|
logger.debug("Command: Close File")
|
||||||
|
view.command.remove_file(view)
|
||||||
|
update_info_bar_if_focused(view.command, view)
|
||||||
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("Command: Cut to Temp Buffer")
|
||||||
|
|
||||||
|
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()
|
||||||
34
src/core/widgets/code/commands/dnd_load_file_to_buffer.py
Normal file
34
src/core/widgets/code/commands/dnd_load_file_to_buffer.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version('GtkSource', '4')
|
||||||
|
|
||||||
|
from gi.repository import GtkSource
|
||||||
|
from gi.repository import Gio
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from ..command_helpers import update_info_bar_if_focused
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View,
|
||||||
|
uri: str
|
||||||
|
):
|
||||||
|
logger.debug("Command: DnD Load File To Buffer")
|
||||||
|
|
||||||
|
file = view.command.get_file(view)
|
||||||
|
buffer = file.buffer
|
||||||
|
|
||||||
|
if not file.ftype == "buffer":
|
||||||
|
file = view.command.new_file(view)
|
||||||
|
|
||||||
|
gfile = Gio.File.new_for_uri(uri)
|
||||||
|
view.command.exec_with_args(
|
||||||
|
"load_file",
|
||||||
|
(view, gfile, file)
|
||||||
|
)
|
||||||
|
|
||||||
|
update_info_bar_if_focused(view.command, view)
|
||||||
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("Command: DnD Load Files")
|
||||||
|
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("Command: Duplicate Line")
|
||||||
|
|
||||||
|
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("Command: Focus Left Sibling")
|
||||||
|
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("Command: Focus Right Sibling")
|
||||||
|
if not view.sibling_right: return
|
||||||
|
view.sibling_right.grab_focus()
|
||||||
21
src/core/widgets/code/commands/get_current_file.py
Normal file
21
src/core/widgets/code/commands/get_current_file.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("Command: Get Current File")
|
||||||
|
|
||||||
|
file = view.command.get_file(view)
|
||||||
|
|
||||||
|
return file
|
||||||
19
src/core/widgets/code/commands/get_filetype.py
Normal file
19
src/core/widgets/code/commands/get_filetype.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("Command: Get File Type")
|
||||||
|
file = view.command.get_file(view)
|
||||||
|
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("Command: Get Text")
|
||||||
|
|
||||||
|
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("Command: Go-To")
|
||||||
|
|
||||||
|
file = view.command.get_file(view)
|
||||||
|
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("Command: Has Focus")
|
||||||
|
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("Command: Line Down")
|
||||||
|
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("Command: Line Up")
|
||||||
|
view.emit("move-lines", False)
|
||||||
28
src/core/widgets/code/commands/load_file.py
Normal file
28
src/core/widgets/code/commands/load_file.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 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
|
||||||
|
from ..command_helpers import set_language_and_style
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View,
|
||||||
|
gfile: Gio.File,
|
||||||
|
file: SourceFile = None,
|
||||||
|
):
|
||||||
|
logger.debug("Command: Load File")
|
||||||
|
if not file:
|
||||||
|
file = view.command.new_file(view)
|
||||||
|
|
||||||
|
file.load_path(gfile)
|
||||||
|
|
||||||
|
set_language_and_style(view, file)
|
||||||
40
src/core/widgets/code/commands/load_start_files.py
Normal file
40
src/core/widgets/code/commands/load_start_files.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# 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,
|
||||||
|
):
|
||||||
|
logger.debug("Command: Load Start File(s)")
|
||||||
|
|
||||||
|
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)
|
||||||
|
file = view.command.get_file(view)
|
||||||
|
|
||||||
|
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))
|
||||||
29
src/core/widgets/code/commands/move_to_left_sibling.py
Normal file
29
src/core/widgets/code/commands/move_to_left_sibling.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version('GtkSource', '4')
|
||||||
|
|
||||||
|
from gi.repository import GtkSource
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View = None
|
||||||
|
):
|
||||||
|
logger.debug("Command: Move To Left Sibling")
|
||||||
|
if not view.sibling_left: return
|
||||||
|
|
||||||
|
buffer = view.get_buffer()
|
||||||
|
popped_file, next_file = view.command.get_swap_file(view)
|
||||||
|
|
||||||
|
view.sibling_left.set_buffer(buffer)
|
||||||
|
view.sibling_left.grab_focus()
|
||||||
|
|
||||||
|
if next_file:
|
||||||
|
view.set_buffer(next_file.buffer)
|
||||||
|
else:
|
||||||
|
view.command.exec("new_file")
|
||||||
29
src/core/widgets/code/commands/move_to_right_sibling.py
Normal file
29
src/core/widgets/code/commands/move_to_right_sibling.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version('GtkSource', '4')
|
||||||
|
|
||||||
|
from gi.repository import GtkSource
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View = None
|
||||||
|
):
|
||||||
|
logger.debug("Command: Move To Right Sibling")
|
||||||
|
if not view.sibling_right: return
|
||||||
|
|
||||||
|
buffer = view.get_buffer()
|
||||||
|
popped_file, next_file = view.command.get_swap_file(view)
|
||||||
|
|
||||||
|
view.sibling_right.set_buffer(buffer)
|
||||||
|
view.sibling_right.grab_focus()
|
||||||
|
|
||||||
|
if next_file:
|
||||||
|
view.set_buffer(next_file.buffer)
|
||||||
|
else:
|
||||||
|
view.command.exec("new_file")
|
||||||
27
src/core/widgets/code/commands/new_file.py
Normal file
27
src/core/widgets/code/commands/new_file.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version('GtkSource', '4')
|
||||||
|
|
||||||
|
from gi.repository import GtkSource
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from ..command_helpers import set_language_and_style, update_info_bar_if_focused
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View = None
|
||||||
|
):
|
||||||
|
logger.debug("Command: New File")
|
||||||
|
|
||||||
|
file = view.command.new_file(view)
|
||||||
|
set_language_and_style(view, file)
|
||||||
|
|
||||||
|
view.set_buffer(file.buffer)
|
||||||
|
|
||||||
|
update_info_bar_if_focused(view.command, view)
|
||||||
|
return file
|
||||||
30
src/core/widgets/code/commands/open_files.py
Normal file
30
src/core/widgets/code/commands/open_files.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
|
||||||
|
from ..command_helpers import update_info_bar_if_focused
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View = None
|
||||||
|
):
|
||||||
|
logger.debug("Command: Open File(s)")
|
||||||
|
gfiles = event_system.emit_and_await("open-files")
|
||||||
|
if not gfiles: return
|
||||||
|
|
||||||
|
file = view.command.get_file(view)
|
||||||
|
if file.ftype == "buffer":
|
||||||
|
gfile = gfiles.pop()
|
||||||
|
view.command.exec_with_args("load_file", (view, gfile, file))
|
||||||
|
view.set_buffer(file.buffer)
|
||||||
|
update_info_bar_if_focused(view.command, view)
|
||||||
|
|
||||||
|
for i, gfile in enumerate(gfiles):
|
||||||
|
view.command.exec_with_args("load_file", (view, gfile))
|
||||||
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("Command: Paste Temp Buffer")
|
||||||
|
|
||||||
|
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()
|
||||||
28
src/core/widgets/code/commands/save_file.py
Normal file
28
src/core/widgets/code/commands/save_file.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
|
||||||
|
from ..command_helpers import set_language_and_style
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View = None
|
||||||
|
):
|
||||||
|
logger.debug("Command: Save File")
|
||||||
|
file = view.command.get_file(view)
|
||||||
|
buffer = file.buffer
|
||||||
|
|
||||||
|
if file.ftype == "buffer":
|
||||||
|
file.save_as()
|
||||||
|
set_language_and_style(view, file)
|
||||||
|
return
|
||||||
|
|
||||||
|
file.save()
|
||||||
|
|
||||||
26
src/core/widgets/code/commands/save_file_as.py
Normal file
26
src/core/widgets/code/commands/save_file_as.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
|
||||||
|
from ..command_helpers import set_language_and_style, update_info_bar_if_focused
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View = None
|
||||||
|
):
|
||||||
|
logger.info("Command: Save File As")
|
||||||
|
file = view.command.get_file(view)
|
||||||
|
buffer = file.buffer
|
||||||
|
|
||||||
|
file.save_as()
|
||||||
|
|
||||||
|
set_language_and_style(view, file)
|
||||||
|
|
||||||
|
update_info_bar_if_focused(view.command, view)
|
||||||
30
src/core/widgets/code/commands/set_buffer.py
Normal file
30
src/core/widgets/code/commands/set_buffer.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
|
||||||
|
from ..source_file import SourceFile
|
||||||
|
from ..command_helpers import update_info_bar_if_focused
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View,
|
||||||
|
file: SourceFile
|
||||||
|
):
|
||||||
|
logger.debug("Command: Set Buffer")
|
||||||
|
|
||||||
|
if not file:
|
||||||
|
view.command.new_file(view)
|
||||||
|
return
|
||||||
|
|
||||||
|
view.set_buffer(file.buffer)
|
||||||
|
|
||||||
|
update_info_bar_if_focused(view.command, view)
|
||||||
|
|
||||||
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("Command: Set Buffer Language")
|
||||||
|
|
||||||
|
buffer = view.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("Command: Set Buffer Style")
|
||||||
|
|
||||||
|
buffer = view.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("Command: Set Focus Border")
|
||||||
|
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")
|
||||||
22
src/core/widgets/code/commands/set_miniview.py
Normal file
22
src/core/widgets/code/commands/set_miniview.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# 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 import FocusedViewEvent
|
||||||
|
|
||||||
|
from ..source_file import SourceFile
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def execute(
|
||||||
|
view: GtkSource.View,
|
||||||
|
):
|
||||||
|
logger.debug("Command: Set MiniView")
|
||||||
|
event_system.emit("set-mini-view", (view,))
|
||||||
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("Command: Show Completion")
|
||||||
|
view.command.request_completion(view)
|
||||||
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("Command: Update Info Bar")
|
||||||
|
file = view.command.get_file(view)
|
||||||
|
buffer = 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("Command: Zoom In")
|
||||||
|
|
||||||
|
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("Command: Zoom Out")
|
||||||
|
|
||||||
|
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}")
|
||||||
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
|
||||||
3
src/core/widgets/code/controllers/__init__.py
Normal file
3
src/core/widgets/code/controllers/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Controllers Package
|
||||||
|
"""
|
||||||
34
src/core/widgets/code/controllers/commands_controller.py
Normal file
34
src/core/widgets/code/controllers/commands_controller.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.code import (
|
||||||
|
CodeEvent,
|
||||||
|
GetCommandSystemEvent,
|
||||||
|
FocusedViewEvent
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..command_system import CommandSystem
|
||||||
|
|
||||||
|
from .controller_base import ControllerBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CommandsController(ControllerBase, list):
|
||||||
|
def __init__(self):
|
||||||
|
super(CommandsController, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
def _controller_message(self, event: CodeEvent):
|
||||||
|
if isinstance(event, GetCommandSystemEvent):
|
||||||
|
event.response = self.get_command_system()
|
||||||
|
|
||||||
|
def get_command_system(self):
|
||||||
|
command_system = CommandSystem()
|
||||||
|
command_system.emit = self.emit
|
||||||
|
command_system.emit_to = self.emit_to
|
||||||
|
|
||||||
|
self.append(command_system)
|
||||||
|
|
||||||
|
return command_system
|
||||||
111
src/core/widgets/code/controllers/completion_controller.py
Normal file
111
src/core/widgets/code/controllers/completion_controller.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('GtkSource', '4')
|
||||||
|
|
||||||
|
from gi.repository import GLib
|
||||||
|
from gi.repository import GtkSource
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.code import (
|
||||||
|
CodeEvent,
|
||||||
|
FocusedViewEvent,
|
||||||
|
RequestCompletionEvent,
|
||||||
|
CursorMovedEvent,
|
||||||
|
TextChangedEvent,
|
||||||
|
TextInsertedEvent
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..completion_providers.example_completion_provider import ExampleCompletionProvider
|
||||||
|
from ..completion_providers.lsp_completion_provider import LSPCompletionProvider
|
||||||
|
|
||||||
|
from .controller_base import ControllerBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CompletionController(ControllerBase):
|
||||||
|
def __init__(self):
|
||||||
|
super(CompletionController, self).__init__()
|
||||||
|
|
||||||
|
self._completor: GtkSource.Completion = None
|
||||||
|
self._timeout_id: int = None
|
||||||
|
self._lsp_provider: LSPCompletionProvider = LSPCompletionProvider()
|
||||||
|
|
||||||
|
|
||||||
|
def _controller_message(self, event: CodeEvent):
|
||||||
|
if isinstance(event, FocusedViewEvent):
|
||||||
|
self._completor = event.view.get_completion()
|
||||||
|
|
||||||
|
if not self._timeout_id: return
|
||||||
|
|
||||||
|
GLib.source_remove(self._timeout_id)
|
||||||
|
self._timeout_id = None
|
||||||
|
elif isinstance(event, RequestCompletionEvent):
|
||||||
|
self.request_completion()
|
||||||
|
# elif isinstance(event, TextInsertedEvent):
|
||||||
|
# self.request_completion()
|
||||||
|
|
||||||
|
def _process_request_completion(self):
|
||||||
|
self._start_completion()
|
||||||
|
|
||||||
|
self._timeout_id = None
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _do_completion(self):
|
||||||
|
if self._completor.get_providers():
|
||||||
|
self._match_completion()
|
||||||
|
else:
|
||||||
|
self._start_completion()
|
||||||
|
|
||||||
|
def _match_completion(self):
|
||||||
|
"""
|
||||||
|
Note: Use IF providers were added to completion...
|
||||||
|
"""
|
||||||
|
self._completion.match(
|
||||||
|
self._completion.create_context()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _start_completion(self):
|
||||||
|
"""
|
||||||
|
Note: Use IF NO providers have been added to completion...
|
||||||
|
"""
|
||||||
|
self._completor.start(
|
||||||
|
[
|
||||||
|
ExampleCompletionProvider(),
|
||||||
|
self._lsp_provider
|
||||||
|
],
|
||||||
|
self._completor.create_context()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_completer(self, completer):
|
||||||
|
self._completor = completer
|
||||||
|
|
||||||
|
def request_completion(self):
|
||||||
|
if self._timeout_id:
|
||||||
|
GLib.source_remove(self._timeout_id)
|
||||||
|
|
||||||
|
self._timeout_id = GLib.timeout_add(
|
||||||
|
800,
|
||||||
|
self._process_request_completion
|
||||||
|
)
|
||||||
|
|
||||||
|
def register_provider(
|
||||||
|
self,
|
||||||
|
provider_name: str,
|
||||||
|
provider: GtkSource.CompletionProvider,
|
||||||
|
priority: int = 0,
|
||||||
|
language_ids: list = None
|
||||||
|
):
|
||||||
|
"""Register completion providers with priority and language filtering"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def unregister_provider(self, provider_name: str):
|
||||||
|
"""Remove completion providers"""
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_active_providers(self, language_id: str = None) -> list:
|
||||||
|
"""Get providers filtered by language"""
|
||||||
|
...
|
||||||
38
src/core/widgets/code/controllers/controller_base.py
Normal file
38
src/core/widgets/code/controllers/controller_base.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.singleton import Singleton
|
||||||
|
|
||||||
|
from libs.dto.code.code_event import CodeEvent
|
||||||
|
|
||||||
|
from .emit_dispatcher import EmitDispatcher
|
||||||
|
from .controller_context import ControllerContext
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ControllerBaseException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ControllerBase(Singleton, EmitDispatcher):
|
||||||
|
def __init__(self):
|
||||||
|
super(ControllerBase, self).__init__()
|
||||||
|
|
||||||
|
self.controller_context: ControllerContext = None
|
||||||
|
|
||||||
|
|
||||||
|
def _controller_message(self, event: CodeEvent):
|
||||||
|
raise ControllerBaseException("Controller Base must override '_controller_message'...")
|
||||||
|
|
||||||
|
def set_controller_context(self, controller_context: ControllerContext):
|
||||||
|
self.controller_context = controller_context
|
||||||
|
|
||||||
|
def message_to(self, name: str, event: CodeEvent):
|
||||||
|
return self.controller_context.message_to(name, event)
|
||||||
|
|
||||||
|
def message_all(self, event: CodeEvent):
|
||||||
|
return self.controller_context.message_all(event)
|
||||||
|
|
||||||
24
src/core/widgets/code/controllers/controller_context.py
Normal file
24
src/core/widgets/code/controllers/controller_context.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.code.code_event import CodeEvent
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ControllerContextException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ControllerContext:
|
||||||
|
def __init__(self):
|
||||||
|
super(ControllerContext, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
def message_to(self, name: str, event: CodeEvent):
|
||||||
|
raise ControllerContextException("Controller Context 'message_to' must be overriden by Controller Manager...")
|
||||||
|
|
||||||
|
def message_all(self, event: CodeEvent):
|
||||||
|
raise ControllerContextException("Controller Context 'message_all' must be overriden by Controller Manager...")
|
||||||
48
src/core/widgets/code/controllers/controller_manager.py
Normal file
48
src/core/widgets/code/controllers/controller_manager.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.singleton import Singleton
|
||||||
|
|
||||||
|
from libs.dto.code.code_event import CodeEvent
|
||||||
|
|
||||||
|
from .controller_base import ControllerBase
|
||||||
|
from .controller_context import ControllerContext
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ControllerManagerException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ControllerManager(Singleton, dict):
|
||||||
|
def __init__(self):
|
||||||
|
super(ControllerManager, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
def _crete_controller_context(self) -> ControllerContext:
|
||||||
|
controller_context = ControllerContext()
|
||||||
|
controller_context.message_to = self.message_to
|
||||||
|
controller_context.message_all = self.message_all
|
||||||
|
|
||||||
|
return controller_context
|
||||||
|
|
||||||
|
def register_controller(self, name: str, controller: ControllerBase):
|
||||||
|
if not name or controller == None:
|
||||||
|
raise ControllerManagerException("Must pass in a 'name' and 'controller'...")
|
||||||
|
|
||||||
|
controller.set_controller_context(
|
||||||
|
self._crete_controller_context()
|
||||||
|
)
|
||||||
|
|
||||||
|
self[name] = controller
|
||||||
|
|
||||||
|
|
||||||
|
def message_to(self, name: str, event: CodeEvent):
|
||||||
|
self[name]._controller_message(event)
|
||||||
|
|
||||||
|
def message_all(self, event: CodeEvent):
|
||||||
|
for key in self.keys():
|
||||||
|
self[key]._controller_message(event)
|
||||||
19
src/core/widgets/code/controllers/emit_dispatcher.py
Normal file
19
src/core/widgets/code/controllers/emit_dispatcher.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.code.code_event import CodeEvent
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class EmitDispatcher:
|
||||||
|
def __init__(self):
|
||||||
|
super(EmitDispatcher, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
def emit(self, event: CodeEvent):
|
||||||
|
self.message_all(event)
|
||||||
|
|
||||||
|
def emit_to(self, controller: str, event: CodeEvent):
|
||||||
|
self.message_to(controller, event)
|
||||||
150
src/core/widgets/code/controllers/files_controller.py
Normal file
150
src/core/widgets/code/controllers/files_controller.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.code import CodeEvent
|
||||||
|
from ..event_factory import Event_Factory, Event_Factory_Types
|
||||||
|
|
||||||
|
from ..source_file import SourceFile
|
||||||
|
from ..source_buffer import SourceBuffer
|
||||||
|
|
||||||
|
from .controller_base import ControllerBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FilesController(ControllerBase, list):
|
||||||
|
def __init__(self):
|
||||||
|
super(FilesController, self).__init__()
|
||||||
|
|
||||||
|
|
||||||
|
def _controller_message(self, event: CodeEvent):
|
||||||
|
if isinstance(event, Event_Factory_Types.AddNewFileEvent):
|
||||||
|
self.new_file(event)
|
||||||
|
elif isinstance(event, Event_Factory_Types.SwapFileEvent):
|
||||||
|
self.swap_file(event)
|
||||||
|
elif isinstance(event, Event_Factory_Types.PopFileEvent):
|
||||||
|
self.pop_file(event)
|
||||||
|
elif isinstance(event, Event_Factory_Types.RemoveFileEvent):
|
||||||
|
self.remove_file(event)
|
||||||
|
elif isinstance(event, Event_Factory_Types.GetFileEvent):
|
||||||
|
self.get_file(event)
|
||||||
|
elif isinstance(event, Event_Factory_Types.GetSwapFileEvent):
|
||||||
|
self.get_swap_file(event)
|
||||||
|
|
||||||
|
def get_file(self, event: Event_Factory_Types.GetFileEvent):
|
||||||
|
if not event.buffer: return
|
||||||
|
|
||||||
|
for file in self:
|
||||||
|
if not event.buffer == file.buffer: continue
|
||||||
|
|
||||||
|
event.response = file
|
||||||
|
|
||||||
|
return file
|
||||||
|
|
||||||
|
def get_swap_file(self, event: Event_Factory_Types.GetSwapFileEvent):
|
||||||
|
if not event.buffer: return
|
||||||
|
|
||||||
|
for i, file in enumerate(self):
|
||||||
|
if not event.buffer == file.buffer: continue
|
||||||
|
|
||||||
|
j = self.next_index(i)
|
||||||
|
next_file = self[j]
|
||||||
|
swapped_file = self[j] if not j == -1 else None
|
||||||
|
|
||||||
|
event.response = [swapped_file, next_file]
|
||||||
|
|
||||||
|
return swapped_file, next_file
|
||||||
|
|
||||||
|
def new_file(self, event: Event_Factory_Types.AddNewFileEvent):
|
||||||
|
file = SourceFile()
|
||||||
|
file.emit = self.emit
|
||||||
|
file.emit_to = self.emit_to
|
||||||
|
|
||||||
|
event.response = file
|
||||||
|
|
||||||
|
eve = Event_Factory.create_event(
|
||||||
|
"added_new_file",
|
||||||
|
view = event.view,
|
||||||
|
file = file
|
||||||
|
)
|
||||||
|
self.message_all(eve)
|
||||||
|
|
||||||
|
self.append(file)
|
||||||
|
|
||||||
|
return file
|
||||||
|
|
||||||
|
def swap_file(self, event: Event_Factory_Types.GetSwapFileEvent):
|
||||||
|
if not event.buffer: return
|
||||||
|
|
||||||
|
for i, file in enumerate(self):
|
||||||
|
if not event.buffer == file.buffer: continue
|
||||||
|
|
||||||
|
j = self.next_index(i)
|
||||||
|
next_file = self[j]
|
||||||
|
swapped_file = self[j] if not j == -1 else None
|
||||||
|
|
||||||
|
event.response = [swapped_file, next_file]
|
||||||
|
|
||||||
|
return swapped_file, next_file
|
||||||
|
|
||||||
|
def pop_file(self, event: Event_Factory_Types.PopFileEvent):
|
||||||
|
if not event.buffer: return
|
||||||
|
|
||||||
|
for i, file in enumerate(self):
|
||||||
|
if not event.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.response = [popped_file, next_file]
|
||||||
|
|
||||||
|
eve = Event_Factory.create_event(
|
||||||
|
"popped_file",
|
||||||
|
view = view,
|
||||||
|
file = popped_file,
|
||||||
|
next_file = next_file
|
||||||
|
)
|
||||||
|
self.message_all(eve)
|
||||||
|
|
||||||
|
return popped_file, next_file
|
||||||
|
|
||||||
|
def remove_file(self, event: Event_Factory_Types.RemoveFileEvent):
|
||||||
|
if not event.buffer: return
|
||||||
|
|
||||||
|
for i, file in enumerate(self):
|
||||||
|
if not event.buffer == file.buffer: continue
|
||||||
|
|
||||||
|
j = self.next_index(i)
|
||||||
|
next_file = self[j] if not j == -1 else None
|
||||||
|
|
||||||
|
event.response = next_file
|
||||||
|
|
||||||
|
eve = Event_Factory.create_event(
|
||||||
|
"removed_file",
|
||||||
|
view = event.view,
|
||||||
|
ignore_focus = True,
|
||||||
|
file = file,
|
||||||
|
next_file = next_file
|
||||||
|
)
|
||||||
|
self.message_all(eve)
|
||||||
|
|
||||||
|
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
|
||||||
128
src/core/widgets/code/controllers/source_views_controller.py
Normal file
128
src/core/widgets/code/controllers/source_views_controller.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from ..event_factory import Event_Factory, Event_Factory_Types
|
||||||
|
|
||||||
|
from ..command_system import CommandSystem
|
||||||
|
from ..key_mapper import KeyMapper
|
||||||
|
|
||||||
|
from ..source_view import SourceView
|
||||||
|
|
||||||
|
from .controller_base import ControllerBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SourceViewsController(ControllerBase, list):
|
||||||
|
def __init__(self):
|
||||||
|
super(SourceViewsController, self).__init__()
|
||||||
|
|
||||||
|
self.key_mapper: KeyMapper = KeyMapper()
|
||||||
|
self.active_view: SourceView = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_command_system(self):
|
||||||
|
event = Event_Factory.create_event("get_command_system")
|
||||||
|
self.message_to("commands", event)
|
||||||
|
command = event.response
|
||||||
|
|
||||||
|
del event
|
||||||
|
return command
|
||||||
|
|
||||||
|
def create_source_view(self):
|
||||||
|
source_view: SourceView = SourceView()
|
||||||
|
source_view.command = self.get_command_system()
|
||||||
|
source_view.command.set_data(source_view)
|
||||||
|
|
||||||
|
self._map_signals(source_view)
|
||||||
|
|
||||||
|
self.append(source_view)
|
||||||
|
return source_view
|
||||||
|
|
||||||
|
def _controller_message(self, event: Event_Factory_Types.CodeEvent):
|
||||||
|
if isinstance(event, Event_Factory_Types.RemovedFileEvent):
|
||||||
|
self._remove_file(event)
|
||||||
|
elif isinstance(event, Event_Factory_Types.TextChangedEvent):
|
||||||
|
self.active_view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
def _map_signals(self, source_view: SourceView):
|
||||||
|
source_view.connect("focus-in-event", self._focus_in_event)
|
||||||
|
source_view.connect("move-cursor", self._move_cursor)
|
||||||
|
source_view.connect("key-press-event", self._key_press_event)
|
||||||
|
source_view.connect("key-release-event", self._key_release_event)
|
||||||
|
source_view.connect("button-press-event", self._button_press_event)
|
||||||
|
source_view.connect("button-release-event", self._button_release_event)
|
||||||
|
|
||||||
|
def _focus_in_event(self, view, eve):
|
||||||
|
self.active_view = view
|
||||||
|
|
||||||
|
view.command.exec("set_miniview")
|
||||||
|
view.command.exec("set_focus_border")
|
||||||
|
view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
event = Event_Factory.create_focused_view(view = view)
|
||||||
|
self.emit(event)
|
||||||
|
|
||||||
|
def _move_cursor(self, view, step, count, extend_selection):
|
||||||
|
buffer = view.get_buffer()
|
||||||
|
iter = buffer.get_iter_at_mark( buffer.get_insert() )
|
||||||
|
line = iter.get_line()
|
||||||
|
char = iter.get_line_offset()
|
||||||
|
|
||||||
|
event = Event_Factory.create_cursor_moved(
|
||||||
|
view = view,
|
||||||
|
buffer = buffer,
|
||||||
|
line = line,
|
||||||
|
char = char
|
||||||
|
)
|
||||||
|
|
||||||
|
self.emit(event)
|
||||||
|
|
||||||
|
view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
def _button_press_event(self, view, eve):
|
||||||
|
self.active_view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
def _button_release_event(self, view, eve):
|
||||||
|
self.active_view.command.exec("update_info_bar")
|
||||||
|
|
||||||
|
def _key_press_event(self, view, eve):
|
||||||
|
command = self.key_mapper._key_press_event(eve)
|
||||||
|
is_future = self.key_mapper._key_release_event(eve)
|
||||||
|
|
||||||
|
if is_future: return True
|
||||||
|
if not command: return False
|
||||||
|
|
||||||
|
view.command.exec(command)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _key_release_event(self, view, eve):
|
||||||
|
command = self.key_mapper._key_release_event(eve)
|
||||||
|
is_past = self.key_mapper._key_press_event(eve)
|
||||||
|
|
||||||
|
if is_past: return True
|
||||||
|
if not command: return False
|
||||||
|
|
||||||
|
view.command.exec(command)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _remove_file(self, event: Event_Factory_Types.RemovedFileEvent):
|
||||||
|
for view in self:
|
||||||
|
if not event.file.buffer == view.get_buffer(): continue
|
||||||
|
if not event.next_file:
|
||||||
|
view.command.exec("new_file")
|
||||||
|
continue
|
||||||
|
|
||||||
|
view.set_buffer(event.next_file.buffer)
|
||||||
|
|
||||||
|
def first_map_load(self):
|
||||||
|
for view in self:
|
||||||
|
view.command.exec("new_file")
|
||||||
|
|
||||||
|
view = self[0]
|
||||||
|
view.grab_focus()
|
||||||
|
view.command.exec("load_start_files")
|
||||||
|
|
||||||
86
src/core/widgets/code/controllers/tabs_controller.py
Normal file
86
src/core/widgets/code/controllers/tabs_controller.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.code import CodeEvent
|
||||||
|
from ..event_factory import Event_Factory, Event_Factory_Types
|
||||||
|
|
||||||
|
from ..tabs_widget import TabsWidget
|
||||||
|
from ..tab_widget import TabWidget
|
||||||
|
|
||||||
|
from ..source_view import SourceView
|
||||||
|
|
||||||
|
from .controller_base import ControllerBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TabsController(ControllerBase):
|
||||||
|
def __init__(self):
|
||||||
|
super(TabsController, self).__init__()
|
||||||
|
|
||||||
|
self.active_view: SourceView = None
|
||||||
|
self.tabs_widget: TabsWidget = TabsWidget()
|
||||||
|
|
||||||
|
|
||||||
|
def _controller_message(self, event: CodeEvent):
|
||||||
|
if isinstance(event, Event_Factory_Types.FocusedViewEvent):
|
||||||
|
self.active_view = event.view
|
||||||
|
elif isinstance(event, Event_Factory_Types.FilePathSetEvent):
|
||||||
|
self.update_tab_label(event)
|
||||||
|
elif isinstance(event, Event_Factory_Types.AddedNewFileEvent):
|
||||||
|
self.add_tab(event)
|
||||||
|
elif isinstance(event, Event_Factory_Types.PoppedFileEvent):
|
||||||
|
...
|
||||||
|
elif isinstance(event, Event_Factory_Types.RemovedFileEvent):
|
||||||
|
self.remove_tab(event)
|
||||||
|
|
||||||
|
def get_tabs_widget(self):
|
||||||
|
return self.tabs_widget
|
||||||
|
|
||||||
|
def update_tab_label(self, event: Event_Factory_Types.FilePathSetEvent):
|
||||||
|
for tab in self.tabs_widget.get_children():
|
||||||
|
if not event.file == tab.file: continue
|
||||||
|
tab.label.set_label(event.file.fname)
|
||||||
|
break
|
||||||
|
|
||||||
|
def add_tab(self, event: Event_Factory_Types.AddedNewFileEvent):
|
||||||
|
def set_active_tab(tab, eve, file):
|
||||||
|
event = Event_Factory.create_event(
|
||||||
|
"set_active_file",
|
||||||
|
buffer = tab.get_parent().file.buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
self.active_view.set_buffer(
|
||||||
|
tab.get_parent().file.buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
self.message_all(event)
|
||||||
|
|
||||||
|
def close_tab(tab, eve, file):
|
||||||
|
event = Event_Factory.create_event(
|
||||||
|
"remove_file",
|
||||||
|
buffer = tab.get_parent().file.buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
self.message_all(event)
|
||||||
|
|
||||||
|
tab = TabWidget()
|
||||||
|
tab.file = event.file
|
||||||
|
tab.label.set_label(event.file.fname)
|
||||||
|
tab.set_select_signal(set_active_tab)
|
||||||
|
tab.set_close_signal(close_tab)
|
||||||
|
|
||||||
|
self.tabs_widget.add(tab)
|
||||||
|
tab.show()
|
||||||
|
|
||||||
|
def remove_tab(self, event: Event_Factory_Types.RemovedFileEvent):
|
||||||
|
for tab in self.tabs_widget.get_children():
|
||||||
|
if not event.file == tab.file: continue
|
||||||
|
|
||||||
|
tab.clear_signals_and_data()
|
||||||
|
tab.run_dispose()
|
||||||
|
tab.destroy()
|
||||||
|
|
||||||
|
del tab
|
||||||
|
break
|
||||||
101
src/core/widgets/code/event_factory.py
Normal file
101
src/core/widgets/code/event_factory.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Python imports
|
||||||
|
import inspect
|
||||||
|
from typing import Dict, Type
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.singleton import Singleton
|
||||||
|
from libs.dto.code import CodeEvent
|
||||||
|
from libs.dto import code
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class EventFactory(Singleton):
|
||||||
|
def __init__(self):
|
||||||
|
self._event_classes: Dict[str, Type[CodeEvent]] = {}
|
||||||
|
|
||||||
|
self._auto_register_events()
|
||||||
|
|
||||||
|
def register_event(self, event_type: str, event_class: Type[CodeEvent]):
|
||||||
|
self._event_classes[event_type] = event_class
|
||||||
|
|
||||||
|
def create_event(self, event_type: str, **kwargs) -> CodeEvent:
|
||||||
|
if event_type not in self._event_classes:
|
||||||
|
raise ValueError(f"Unknown event type: {event_type}")
|
||||||
|
|
||||||
|
event_class = self._event_classes[event_type]
|
||||||
|
event = event_class()
|
||||||
|
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
if hasattr(event, key):
|
||||||
|
setattr(event, key, value)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Event class {event_class.__name__} has no attribute '{key}'")
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
def _auto_register_events(self):
|
||||||
|
for name, obj in code.__dict__.items():
|
||||||
|
if not self._is_valid_event_class(obj): continue
|
||||||
|
|
||||||
|
event_type = self._class_name_to_event_type(name)
|
||||||
|
self.register_event(event_type, obj)
|
||||||
|
|
||||||
|
logger.debug(f"Auto-registered {len(self._event_classes)} event types")
|
||||||
|
|
||||||
|
def _is_valid_event_class(self, obj) -> bool:
|
||||||
|
return (inspect.isclass(obj) and
|
||||||
|
issubclass(obj, CodeEvent) and
|
||||||
|
obj != CodeEvent)
|
||||||
|
|
||||||
|
def _class_name_to_event_type(self, class_name: str) -> str:
|
||||||
|
base_name = class_name[:-5] if class_name.endswith('Event') else class_name
|
||||||
|
return re.sub(r'(?<!^)(?=[A-Z])', '_', base_name).lower()
|
||||||
|
|
||||||
|
def create_cursor_moved(self, **kwargs):
|
||||||
|
return self.create_event("cursor_moved", **kwargs)
|
||||||
|
|
||||||
|
def create_text_changed(self, **kwargs):
|
||||||
|
return self.create_event("text_changed", **kwargs)
|
||||||
|
|
||||||
|
def create_focused_view(self, **kwargs):
|
||||||
|
return self.create_event("focused_view", **kwargs)
|
||||||
|
|
||||||
|
def create_modified_changed(self, **kwargs):
|
||||||
|
return self.create_event("modified_changed", **kwargs)
|
||||||
|
|
||||||
|
def create_get_command_system(self, **kwargs):
|
||||||
|
return self.create_event("get_command_system", **kwargs)
|
||||||
|
|
||||||
|
def create_file_path_set(self, **kwargs):
|
||||||
|
return self.create_event("file_path_set", **kwargs)
|
||||||
|
|
||||||
|
def create_text_inserted(self, **kwargs):
|
||||||
|
return self.create_event("text_inserted", **kwargs)
|
||||||
|
|
||||||
|
def create_set_active_file(self, **kwargs):
|
||||||
|
return self.create_event("set_active_file", **kwargs)
|
||||||
|
|
||||||
|
def create_added_new_file(self, **kwargs):
|
||||||
|
return self.create_event("added_new_file", **kwargs)
|
||||||
|
|
||||||
|
def create_popped_file(self, **kwargs):
|
||||||
|
return self.create_event("popped_file", **kwargs)
|
||||||
|
|
||||||
|
def create_get_file(self, **kwargs):
|
||||||
|
return self.create_event("get_file", **kwargs)
|
||||||
|
|
||||||
|
def create_get_swap_file(self, **kwargs):
|
||||||
|
return self.create_event("get_swap_file", **kwargs)
|
||||||
|
|
||||||
|
def create_remove_file(self, **kwargs):
|
||||||
|
return self.create_event("remove_file", **kwargs)
|
||||||
|
|
||||||
|
def create_removed_file(self, **kwargs):
|
||||||
|
return self.create_event("removed_file", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
Event_Factory = EventFactory()
|
||||||
|
Event_Factory_Types = code
|
||||||
100
src/core/widgets/code/info_bar_widget.py
Normal file
100
src/core/widgets/code/info_bar_widget.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# 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 InfoBarWidget(Gtk.Box):
|
||||||
|
""" docstring for InfoBarWidget. """
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(InfoBarWidget, 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):
|
||||||
|
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
|
||||||
|
|
||||||
39
src/core/widgets/code/mini_view_widget.py
Normal file
39
src/core/widgets/code/mini_view_widget.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# 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):
|
||||||
|
ctx = self.get_style_context()
|
||||||
|
ctx.add_class("mini-view")
|
||||||
|
|
||||||
|
self.set_hexpand(False)
|
||||||
|
|
||||||
|
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))
|
||||||
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)
|
||||||
129
src/core/widgets/code/source_file.py
Normal file
129
src/core/widgets/code/source_file.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# 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.dto.code import CodeEvent
|
||||||
|
from .event_factory import Event_Factory, Event_Factory_Types
|
||||||
|
|
||||||
|
from .source_buffer import SourceBuffer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SourceFile(GtkSource.File):
|
||||||
|
def __init__(self):
|
||||||
|
super(SourceFile, self).__init__()
|
||||||
|
|
||||||
|
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 _changed(self, buffer: SourceBuffer):
|
||||||
|
event = Event_Factory.create_text_changed(buffer = buffer)
|
||||||
|
event.file = self
|
||||||
|
self.emit(event)
|
||||||
|
|
||||||
|
def _insert_text(self, buffer: SourceBuffer, location: Gtk.TextIter,
|
||||||
|
text: str, length: int
|
||||||
|
):
|
||||||
|
event = Event_Factory.create_event(
|
||||||
|
"text_inserted",
|
||||||
|
file = self,
|
||||||
|
buffer = buffer
|
||||||
|
)
|
||||||
|
self.emit(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.emit(event)
|
||||||
|
...
|
||||||
|
|
||||||
|
def _modified_changed(self, buffer: SourceBuffer):
|
||||||
|
event = Event_Factory.create_modified_changed(
|
||||||
|
file = self, buffer = buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
self.emit(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 = Event_Factory.create_event("file_path_set", file = self)
|
||||||
|
self.emit(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):
|
||||||
|
del self.buffer
|
||||||
|
|
||||||
|
def emit(self, event: CodeEvent):
|
||||||
|
...
|
||||||
|
|
||||||
|
def emit_to(self, controller: str, event: CodeEvent):
|
||||||
|
...
|
||||||
84
src/core/widgets/code/source_view.py
Normal file
84
src/core/widgets/code/source_view.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
from .mixins.source_view_dnd_mixin import SourceViewDnDMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SourceView(GtkSource.View, SourceViewDnDMixin):
|
||||||
|
def __init__(self):
|
||||||
|
super(SourceView, self).__init__()
|
||||||
|
|
||||||
|
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.connect("drag-data-received", self._on_drag_data_received)
|
||||||
|
|
||||||
|
def _subscribe_to_events(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _load_widgets(self):
|
||||||
|
self.language_manager = GtkSource.LanguageManager()
|
||||||
|
self.style_scheme_manager = GtkSource.StyleSchemeManager()
|
||||||
|
|
||||||
|
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._set_up_dnd()
|
||||||
|
|
||||||
|
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)
|
||||||
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)
|
||||||
87
src/core/widgets/code/tabs_widget.py
Normal file
87
src/core/widgets/code/tabs_widget.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from libs.dto.code.code_event import CodeEvent
|
||||||
|
|
||||||
|
from .source_view import SourceView
|
||||||
|
from .source_file import SourceFile
|
||||||
|
|
||||||
|
from .tab_widget import TabWidget
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TabsWidget(Gtk.ButtonBox):
|
||||||
|
def __init__(self):
|
||||||
|
super(TabsWidget, self).__init__()
|
||||||
|
|
||||||
|
self._setup_styling()
|
||||||
|
self._setup_signals()
|
||||||
|
self._subscribe_to_events()
|
||||||
|
self._load_widgets()
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_styling(self):
|
||||||
|
self.set_layout(Gtk.ButtonBoxStyle.CENTER)
|
||||||
|
|
||||||
|
def _setup_signals(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _subscribe_to_events(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _load_widgets(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def add_tab(self, event: CodeEvent):
|
||||||
|
"""Add a tab widget for the given file event."""
|
||||||
|
if not hasattr(self, 'tabs'):
|
||||||
|
return
|
||||||
|
|
||||||
|
tab = TabWidget()
|
||||||
|
tab.file = event.file
|
||||||
|
|
||||||
|
tab.label.set_label(event.file.fname)
|
||||||
|
|
||||||
|
def select_signal(widget, eve, file):
|
||||||
|
self.code_base.active_view.command.exec_with_args(
|
||||||
|
"set_buffer",
|
||||||
|
(self.code_base.active_view, file)
|
||||||
|
)
|
||||||
|
|
||||||
|
def close_signal(widget, eve, file):
|
||||||
|
self.code_base.files_controller.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):
|
||||||
|
"""Remove a tab widget for the given file event."""
|
||||||
|
if not hasattr(self, 'tabs'):
|
||||||
|
return
|
||||||
|
|
||||||
|
for child in self.tabs.get_children():
|
||||||
|
if not child.file == event.file: continue
|
||||||
|
|
||||||
|
self.tabs.remove(child)
|
||||||
|
child.clear_signals_and_data()
|
||||||
|
del child
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def update_tab_label(self, event: CodeEvent):
|
||||||
|
"""Update tab label for the given file event."""
|
||||||
|
if not hasattr(self, 'tabs'):
|
||||||
|
return
|
||||||
|
|
||||||
|
for tab in self.tabs.get_children():
|
||||||
|
if not tab.file == event.file: continue
|
||||||
|
tab.label.set_label(event.file.fname)
|
||||||
|
|
||||||
|
return
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
"""
|
"""
|
||||||
Widgets.Controls Module
|
Widgets.Controls Package
|
||||||
"""
|
"""
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
|
from contextlib import suppress
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
gi.require_version('Gdk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Gdk
|
|
||||||
from gi.repository import Gio
|
from gi.repository import Gio
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
@@ -36,7 +35,7 @@ class OpenFilesButton(Gtk.Button):
|
|||||||
self.connect("button-release-event", self._open_files)
|
self.connect("button-release-event", self._open_files)
|
||||||
|
|
||||||
def _subscribe_to_events(self):
|
def _subscribe_to_events(self):
|
||||||
event_system.subscribe("open_files", self._open_files)
|
event_system.subscribe("open-files", self._open_files)
|
||||||
|
|
||||||
def _load_widgets(self):
|
def _load_widgets(self):
|
||||||
...
|
...
|
||||||
@@ -60,11 +59,9 @@ class OpenFilesButton(Gtk.Button):
|
|||||||
|
|
||||||
chooser.set_select_multiple(True)
|
chooser.set_select_multiple(True)
|
||||||
|
|
||||||
try:
|
with suppress(Exception):
|
||||||
folder = widget.get_current_file().get_parent() if not start_dir else start_dir
|
folder = widget.get_current_file().get_parent() if not start_dir else start_dir
|
||||||
chooser.set_current_folder( folder.get_path() )
|
chooser.set_current_folder( folder.get_path() )
|
||||||
except Exception as e:
|
|
||||||
...
|
|
||||||
|
|
||||||
response = chooser.run()
|
response = chooser.run()
|
||||||
if not response == Gtk.ResponseType.OK:
|
if not response == Gtk.ResponseType.OK:
|
||||||
|
|||||||
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
|
||||||
@@ -38,11 +38,11 @@ class TransparencyScale(Gtk.Scale):
|
|||||||
adjust = self.get_adjustment()
|
adjust = self.get_adjustment()
|
||||||
adjust.set_lower(0)
|
adjust.set_lower(0)
|
||||||
adjust.set_upper(100)
|
adjust.set_upper(100)
|
||||||
adjust.set_value(settings.theming.transparency)
|
adjust.set_value(settings_manager.settings.theming.transparency)
|
||||||
adjust.set_step_increment(1.0)
|
adjust.set_step_increment(1.0)
|
||||||
|
|
||||||
def _update_transparency(self, range):
|
def _update_transparency(self, range):
|
||||||
event_system.emit("remove-transparency")
|
event_system.emit("remove-transparency")
|
||||||
tp = int(range.get_value())
|
tp = int(range.get_value())
|
||||||
settings.theming.transparency = tp
|
settings_manager.settings.theming.transparency = tp
|
||||||
event_system.emit("update-transparency")
|
event_system.emit("update-transparency")
|
||||||
35
src/core/widgets/pager_widget.py
Normal file
35
src/core/widgets/pager_widget.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Wnck', '3.0')
|
||||||
|
from gi.repository import Wnck
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PagerWidget:
|
||||||
|
def __init__(self):
|
||||||
|
super(PagerWidget, 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):
|
||||||
|
...
|
||||||
|
|
||||||
|
def get_widget(self):
|
||||||
|
return Wnck.Pager.new()
|
||||||
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):
|
||||||
|
...
|
||||||
54
src/core/widgets/task_list_widget.py
Normal file
54
src/core/widgets/task_list_widget.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
gi.require_version('Wnck', '3.0')
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from gi.repository import Wnck
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class TaskListWidget(Gtk.ScrolledWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super(TaskListWidget, self).__init__()
|
||||||
|
|
||||||
|
self._setup_styling()
|
||||||
|
self._setup_signals()
|
||||||
|
self._subscribe_to_events()
|
||||||
|
self._load_widgets()
|
||||||
|
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_styling(self):
|
||||||
|
self.set_hexpand(False)
|
||||||
|
self.set_size_request(180, -1)
|
||||||
|
|
||||||
|
def _setup_signals(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _subscribe_to_events(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _load_widgets(self):
|
||||||
|
viewport = Gtk.Viewport()
|
||||||
|
task_list = Wnck.Tasklist.new()
|
||||||
|
vbox = Gtk.Box()
|
||||||
|
|
||||||
|
vbox.set_orientation(Gtk.Orientation.VERTICAL)
|
||||||
|
|
||||||
|
|
||||||
|
task_list.set_scroll_enabled(False)
|
||||||
|
task_list.set_button_relief(2) # 0 = normal relief, 2 = no relief
|
||||||
|
task_list.set_grouping(1) # 0 = mever group, 1 auto group, 2 = always group
|
||||||
|
|
||||||
|
task_list.set_vexpand(True)
|
||||||
|
task_list.set_include_all_workspaces(False)
|
||||||
|
task_list.set_orientation(1) # 0 = horizontal, 1 = vertical
|
||||||
|
|
||||||
|
vbox.add(task_list)
|
||||||
|
viewport.add(vbox)
|
||||||
|
self.add(viewport)
|
||||||
129
src/core/widgets/vte_widget.py
Normal file
129
src/core/widgets/vte_widget.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# 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 !!!
|
||||||
|
# Also, KEEP the <space> prefix in commands to keep from inserting to bash history.
|
||||||
|
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'))
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
"""
|
"""
|
||||||
WebKit2 UI Module
|
WebKit2 UI Package
|
||||||
"""
|
"""
|
||||||
@@ -37,7 +37,7 @@ class WebkitUI(WebKit2.WebView):
|
|||||||
self.set_settings( WebkitUISettings() )
|
self.set_settings( WebkitUISettings() )
|
||||||
|
|
||||||
def _load_view(self):
|
def _load_view(self):
|
||||||
path = settings_manager.get_context_path()
|
path = settings_manager.path_manager.get_context_path()
|
||||||
data = None
|
data = None
|
||||||
|
|
||||||
with open(f"{path}/index.html", "r") as f:
|
with open(f"{path}/index.html", "r") as f:
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user