refactor(command-system): standardize command execution with *args/**kwargs

- Refactor exec_with_args to use *args/**kwargs instead of tuple arguments
- Add *args/**kwargs to all command execute functions for consistency
- Support multiple key bindings per command in registration
- Add character-based key binding support via get_char() in KeyMapper
- Make execute_plugin async and use asyncio.run for plugin execution
- Use MIME type from Gio content_type instead of language for ftype
This commit is contained in:
2026-02-24 22:30:44 -06:00
parent c233fdbac3
commit 608699c431
55 changed files with 564 additions and 220 deletions

236
AGENTS.md Normal file
View File

@@ -0,0 +1,236 @@
# AGENTS.md - Development Guidelines for Newton
## Overview
Newton is a Python/Gtk3 desktop application (code editor) using PyGObject. The project follows specific conventions documented below.
---
## Build, Lint, and Test Commands
### Running the Application
```bash
# Activate virtual environment and run
source .venv/bin/activate
python -m src
```
### Type Checking (Pyright)
The project uses pyright for static type checking. Configuration is in `pyrightconfig.json`:
```bash
# Run pyright (ensure venv is activated)
pyright src/
# Or via Python module
python -m pyright src/
```
**pyrightconfig.json settings:**
- `venvPath`: "."
- `venv`: ".venv"
- Reports: unused variables, unused imports, duplicate imports
### No Test Framework
**There are currently no tests in this codebase.** If tests are added:
- Use pytest as the testing framework
- Test files should be in a `tests/` directory
- Run a single test: `pytest tests/test_file.py::test_function_name`
---
## Code Style Guidelines
### Import Organization
Imports must be organized with blank lines between groups (in this exact order):
```python
# Python imports
import os
import logging
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GLib
# Application imports
from libs.dto.states import SourceViewStates
from .mixins.source_view_dnd_mixin import SourceViewDnDMixin
```
### Type Hints
- Use type hints for all function parameters and return types
- Use Python's built-in types (`list[str]`, `dict[str, int]`) not typing module aliases
- Example: `def message_to(self, name: str, event: BaseEvent):`
### Naming Conventions
| Element | Convention | Example |
|---------|------------|---------|
| Classes | PascalCase | `ControllerBase`, `SourceView` |
| Methods/Variables | snake_case | `set_controller_context`, `_cut_buffer` |
| Private methods | Leading underscore | `_setup_styles`, `_subscribe_to_events` |
| Constants | UPPER_SNAKE_CASE | `LOG_LEVEL`, `DEFAULT_ZOOM` |
| Exception classes | PascalCase ending in Exception | `ControllerBaseException` |
### Class Structure
```python
class MyClass(Singleton, EmitDispatcher):
def __init__(self):
super(MyClass, self).__init__()
self.controller_context: ControllerContext = None
self._private_var = None
def public_method(self, param: str) -> None:
...
def _private_method(self) -> None:
...
```
### Placeholder Methods
Use ellipsis `...` for unimplemented methods or abstract-like methods:
```python
def _subscribe_to_events(self):
...
```
### Dataclasses for DTOs/Events
Use `@dataclass` for data transfer objects and events:
```python
from dataclasses import dataclass, field
@dataclass
class TextInsertedEvent(CodeEvent):
location: Gtk.TextIter = None
text: str = ""
length: int = 0
```
### Error Handling
- Custom exceptions should end with `Exception`
- Use `raise ValueError("message")` for validation errors
- Let exceptions propagate for unexpected errors
### Singleton Pattern
Classes requiring singleton behavior should inherit from `Singleton`:
```python
from libs.singleton import Singleton
class MySingleton(Singleton):
...
```
### GTK/Widget Conventions
- Call `_setup_styles()`, `_setup_signals()`, `_subscribe_to_events()`, `_load_widgets()` in `__init__`
- Use `self.connect("signal-name", self._handler)` for GTK signals
- Private GTK attributes use underscore prefix: `self._cut_temp_timeout_id`
### Code Formatting
- Use spaces around operators: `x = value`, not `x=value`
- Align assignments with spaces for related variables:
```python
self.state = state
self._cut_temp_timeout_id = None
```
- Maximum line length: Let pyright/editor handle warnings (typically 80-120 chars)
### No Comments
DO NOT add comments to code unless explicitly required. Write self-documenting code.
---
## Project Structure
```
Newton/
├── src/
│ ├── app.py # Main application entry
│ ├── __main__.py # Module entry point
│ ├── __builtins__.py # Built-in definitions
│ ├── core/ # Core UI components
│ │ ├── containers/ # Layout containers
│ │ ├── controllers/ # Controllers
│ │ └── widgets/ # GTK widgets
│ ├── libs/ # Core libraries
│ │ ├── controllers/ # Controller infrastructure
│ │ ├── db/ # Database (SQLModel)
│ │ ├── dto/ # Data transfer objects
│ │ ├── mixins/ # Mixin classes
│ │ └── settings/ # Settings management
│ └── plugins/ # Plugin system
├── plugins/ # Plugin implementations
├── user_config/ # User configuration files
├── requirements.txt # Python dependencies
└── pyrightconfig.json # Pyright configuration
```
---
## Key Libraries Used
- **PyGObject** (GTK3) - GUI framework
- **sqlmodel** - Database (SQLAlchemy + Pydantic)
- **setproctitle** - Process title management
- **pyxdg** - XDG desktop file parsing
---
## Common Patterns
### Creating a New Controller
```python
from libs.controllers.controller_base import ControllerBase
from libs.controllers.controller_context import ControllerContext
class MyController(ControllerBase):
def __init__(self):
super().__init__()
self.controller_context = None
def _controller_message(self, event: BaseEvent):
# Handle events
pass
```
### Creating a New Event/DTO
```python
from dataclasses import dataclass
from libs.dto.code.code_event import CodeEvent
@dataclass
class MyEvent(CodeEvent):
field1: str = ""
field2: int = 0
```
### Connecting to Events
```python
from libs.event_system import EventSystem
event_system = EventSystem()
event_system.subscribe("event_name", self.my_handler)
```

View File

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

View File

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

View File

@@ -0,0 +1,97 @@
# Python imports
# Lib imports
# Application imports
class Autopairs:
def __init__(self):
...
def handle_word_wrap(self, buffer, char_str: str):
wrap_block = self.get_wrap_block(char_str)
if not wrap_block: return
selection = buffer.get_selection_bounds()
if not selection:
self.insert_pair(buffer, char_str, wrap_block)
return True
self.wrap_selection(buffer, char_str, wrap_block, selection)
return True
def insert_pair(
self, buffer, char_str: str, wrap_block: tuple
):
buffer.begin_user_action()
left_block, right_block = wrap_block
insert_mark = buffer.get_insert()
insert_itr = buffer.get_iter_at_mark(insert_mark)
buffer.insert(insert_itr, f"{left_block}{right_block}")
insert_itr = buffer.get_iter_at_mark( insert_mark )
insert_itr.backward_char()
buffer.place_cursor(insert_itr)
buffer.end_user_action()
def wrap_selection(
self, buffer, char_str: str, wrap_block: tuple, selection
):
left_block, \
right_block = wrap_block
start_itr, \
end_itr = selection
data = buffer.get_text(
start_itr, end_itr, include_hidden_chars = False
)
start_mark = buffer.create_mark("startclose", start_itr, False)
end_mark = buffer.create_mark("endclose", end_itr, True)
buffer.begin_user_action()
buffer.insert(start_itr, left_block)
end_itr = buffer.get_iter_at_mark(end_mark)
buffer.insert(end_itr, right_block)
start = buffer.get_iter_at_mark(start_mark)
end = buffer.get_iter_at_mark(end_mark)
buffer.select_range(start, end)
buffer.delete_mark_by_name("startclose")
buffer.delete_mark_by_name("endclose")
buffer.end_user_action()
def get_wrap_block(self, char_str) -> tuple:
left_block = ""
right_block = ""
match char_str:
case "(" | ")":
left_block = "("
right_block = ")"
case "[" | "]":
left_block = "["
right_block = "]"
case "{" | "}":
left_block = "{"
right_block = "}"
case '"':
left_block = '"'
right_block = '"'
case "'":
left_block = "'"
right_block = "'"
case "`":
left_block = "`"
right_block = "`"
case _:
return ()
return left_block, right_block

View File

@@ -1,10 +1,7 @@
{ {
"name": "Autopairs", "name": "Autopairs",
"author": "ITDominator", "author": "ITDominator",
"credit": "Hamad Al Marri",
"version": "0.0.1", "version": "0.0.1",
"support": "", "support": "",
"requests": { "requests": {}
"pass_events": true
}
} }

View File

@@ -0,0 +1,53 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .autopairs import Autopairs
autopairs = Autopairs()
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
event = Event_Factory.create_event("register_command",
command_name = "autopairs",
command = Handler,
binding_mode = "held",
binding = [
"'", "`", "[", "]",
'<Shift>"',
'<Shift>(',
'<Shift>)',
'<Shift>{',
'<Shift>}'
]
)
self.message_to("source_views", event)
def run(self):
...
class Handler:
@staticmethod
def execute(
view: any,
char_str: str
):
logger.debug("Command: Autopairs")
autopairs.handle_word_wrap(view.get_buffer(), char_str)

View File

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

View File

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

View File

@@ -1,140 +0,0 @@
# Python imports
import os
import threading
import subprocess
import time
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from plugins.plugin_base import PluginBase
# NOTE: Threads WILL NOT die with parent's destruction.
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
return wrapper
# NOTE: Threads WILL die with parent's destruction.
def daemon_threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class Plugin(PluginBase):
def __init__(self):
super().__init__()
self.name = "Autopairs" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
# where self.name should not be needed for message comms
self.chars = {
"quotedbl": "\"",
"apostrophe": "'",
"parenleft": "(",
"bracketleft": "[",
"braceleft": "{",
"less": "<",
"grave": "`",
}
self.close = {
"\"": "\"",
"'": "'",
"(": ")",
"[": "]",
"{": "}",
"<": ">",
"`": "`",
}
def generate_reference_ui_element(self):
...
def run(self):
...
def subscribe_to_events(self):
self._event_system.subscribe("set_active_src_view", self._set_active_src_view)
self._event_system.subscribe("autopairs", self._autopairs)
def _set_active_src_view(self, source_view):
self._active_src_view = source_view
self._buffer = self._active_src_view.get_buffer()
self._tag_table = self._buffer.get_tag_table()
def _autopairs(self, keyval_name, ctrl, alt, shift):
if keyval_name in self.chars:
return self.text_insert(self._buffer, keyval_name)
# NOTE: All of below to EOF, lovingly taken from Hamad Al Marri's Gamma
# text editor. I did do some cleanup of comments but otherwise pretty
# much the same code just fitted to my plugin architecture.
# Link: https://gitlab.com/hamadmarri/gamma-text-editor
def text_insert(self, buffer, text):
selection = buffer.get_selection_bounds()
if selection == ():
return self.add_close(buffer, text, )
else:
return self.add_enclose(buffer, text, selection)
def add_close(self, buffer, text):
text = self.chars[text]
text += self.close[text]
position = buffer.get_iter_at_mark( buffer.get_insert() )
c = position.get_char()
if not c in (" ", "", ";", ":", "\t", ",", ".", "\n", "\r") \
and not c in list(self.close.values()):
return False
buffer.insert(position, text)
position = buffer.get_iter_at_mark(buffer.get_insert())
position.backward_char()
buffer.place_cursor(position)
return True
def add_enclose(self, buffer, text, selection):
(start, end) = selection
selected = buffer.get_text(start, end, False)
if len(selected) <= 3 and selected in ("<", ">", ">>>"
"<<", ">>",
"\"", "'", "`",
"(", ")",
"[", "]",
"{", "}",
"=", "==",
"!=", "==="):
return False
start_mark = buffer.create_mark("startclose", start, False)
end_mark = buffer.create_mark("endclose", end, False)
buffer.begin_user_action()
t = self.chars[text]
buffer.insert(start, t)
end = buffer.get_iter_at_mark(end_mark)
t = self.close[t]
buffer.insert(end, t)
start = buffer.get_iter_at_mark(start_mark)
end = buffer.get_iter_at_mark(end_mark)
end.backward_char()
buffer.select_range(start, end)
buffer.end_user_action()
return True

View File

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

View File

@@ -26,16 +26,13 @@ class CommandSystem:
method = getattr(commands, command) method = getattr(commands, command)
args, kwargs = self.data args, kwargs = self.data
if kwargs: return method.execute(*args, **kwargs)
return method.execute(*args, kwargs)
else:
return method.execute(*args)
def exec_with_args(self, command: str, args: list) -> any: def exec_with_args(self, command: str, *args, **kwargs) -> any:
if not hasattr(commands, command): return if not hasattr(commands, command): return
method = getattr(commands, command) method = getattr(commands, command)
return method.execute(*args) return method.execute(*args, **kwargs)
def add_command(self, command_name: str, command: callable): def add_command(self, command_name: str, command: callable):
setattr(commands, command_name, command) setattr(commands, command_name, command)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Buffer Redo") logger.debug("Command: Buffer Redo")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Buffer Undo") logger.debug("Command: Buffer Undo")

View File

@@ -13,7 +13,9 @@ from ..command_helpers import update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Close File") logger.debug("Command: Close File")
view.command.remove_file(view) view.command.remove_file(view)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Cut to Temp Buffer") logger.debug("Command: Cut to Temp Buffer")

View File

@@ -15,7 +15,9 @@ from ..command_helpers import update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
uri: str uri: str,
*args,
**kwargs
): ):
logger.debug("Command: DnD Load File To Buffer") logger.debug("Command: DnD Load File To Buffer")
file = view.command.new_file(view) file = view.command.new_file(view)
@@ -23,7 +25,7 @@ def execute(
gfile = Gio.File.new_for_uri(uri) gfile = Gio.File.new_for_uri(uri)
view.command.exec_with_args( view.command.exec_with_args(
"load_file", "load_file",
(view, gfile, file) view, gfile, file
) )
view.set_buffer(file.buffer) view.set_buffer(file.buffer)

View File

@@ -14,7 +14,9 @@ from gi.repository import Gio
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
uris: list = [] uris: list = [],
*args,
**kwargs
): ):
logger.debug("Command: DnD Load Files") logger.debug("Command: DnD Load Files")
for uri in uris: for uri in uris:
@@ -23,4 +25,4 @@ def execute(
except Exception as e: except Exception as e:
gfile = Gio.File.new_for_path(uri) gfile = Gio.File.new_for_path(uri)
view.command.exec_with_args("load_file", (view, gfile)) view.command.exec_with_args("load_file", view, gfile)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Duplicate Line") logger.debug("Command: Duplicate Line")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Focus Left Sibling") logger.debug("Command: Focus Left Sibling")
if not view.sibling_left: return if not view.sibling_left: return

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Focus Right Sibling") logger.debug("Command: Focus Right Sibling")
if not view.sibling_right: return if not view.sibling_right: return

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Get Current File") logger.debug("Command: Get Current File")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Get File Type") logger.debug("Command: Get File Type")
file = view.command.get_file(view) file = view.command.get_file(view)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Get Text") logger.debug("Command: Get Text")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Go-To") logger.debug("Command: Go-To")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Has Focus") logger.debug("Command: Has Focus")
ctx = view.get_parent().get_style_context() ctx = view.get_parent().get_style_context()

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Line Down") logger.debug("Command: Line Down")
view.emit("move-lines", True) view.emit("move-lines", True)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Line Up") logger.debug("Command: Line Up")
view.emit("move-lines", False) view.emit("move-lines", False)

View File

@@ -18,6 +18,8 @@ def execute(
view: GtkSource.View, view: GtkSource.View,
gfile: Gio.File, gfile: Gio.File,
file: SourceFile = None, file: SourceFile = None,
*args,
**kwargs
): ):
logger.debug("Command: Load File") logger.debug("Command: Load File")
if not file: if not file:

View File

@@ -14,6 +14,8 @@ from gi.repository import Gio
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Load Start File(s)") logger.debug("Command: Load Start File(s)")
@@ -28,7 +30,7 @@ def execute(
view.command.exec_with_args( view.command.exec_with_args(
"load_file", "load_file",
(view, gfile, file) view, gfile, file
) )
if not starting_files: return if not starting_files: return
@@ -37,4 +39,4 @@ def execute(
file = file.replace("FILE|", "") file = file.replace("FILE|", "")
gfile = Gio.File.new_for_path(file) gfile = Gio.File.new_for_path(file)
view.command.exec_with_args("load_file", (view, gfile)) view.command.exec_with_args("load_file", view, gfile)

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Move To Left Sibling") logger.debug("Command: Move To Left Sibling")
if not view.sibling_left: return if not view.sibling_left: return

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Move To Right Sibling") logger.debug("Command: Move To Right Sibling")
if not view.sibling_right: return if not view.sibling_right: return

View File

@@ -14,7 +14,9 @@ from ..command_helpers import set_language_and_style, update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: New File") logger.debug("Command: New File")

View File

@@ -13,7 +13,9 @@ from ..command_helpers import update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Open File(s)") logger.debug("Command: Open File(s)")
gfiles = event_system.emit_and_await("open-files") gfiles = event_system.emit_and_await("open-files")
@@ -22,9 +24,9 @@ def execute(
file = view.command.get_file(view) file = view.command.get_file(view)
if file.ftype == "buffer": if file.ftype == "buffer":
gfile = gfiles.pop() gfile = gfiles.pop()
view.command.exec_with_args("load_file", (view, gfile, file)) view.command.exec_with_args("load_file", view, gfile, file)
view.set_buffer(file.buffer) view.set_buffer(file.buffer)
update_info_bar_if_focused(view.command, view) update_info_bar_if_focused(view.command, view)
for i, gfile in enumerate(gfiles): for i, gfile in enumerate(gfiles):
view.command.exec_with_args("load_file", (view, gfile)) view.command.exec_with_args("load_file", view, gfile)

View File

@@ -13,7 +13,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Paste Temp Buffer") logger.debug("Command: Paste Temp Buffer")

View File

@@ -13,7 +13,9 @@ from ..command_helpers import set_language_and_style
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Save File") logger.debug("Command: Save File")
file = view.command.get_file(view) file = view.command.get_file(view)

View File

@@ -13,7 +13,9 @@ from ..command_helpers import set_language_and_style, update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.info("Command: Save File As") logger.info("Command: Save File As")
file = view.command.get_file(view) file = view.command.get_file(view)

View File

@@ -16,7 +16,9 @@ from ..command_helpers import update_info_bar_if_focused
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
file: SourceFile file: SourceFile,
*args,
**kwargs
): ):
logger.debug("Command: Set Buffer") logger.debug("Command: Set Buffer")

View File

@@ -13,7 +13,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
language: str language: str,
*args,
**kwargs
): ):
logger.debug("Command: Set Buffer Language") logger.debug("Command: Set Buffer Language")

View File

@@ -13,7 +13,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
style: str style: str,
*args,
**kwargs
): ):
logger.debug("Command: Set Buffer Style") logger.debug("Command: Set Buffer Style")

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Set Focus Border") logger.debug("Command: Set Focus Border")
ctx = view.get_parent().get_style_context() ctx = view.get_parent().get_style_context()

View File

@@ -13,6 +13,8 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Set MiniView") logger.debug("Command: Set MiniView")
event_system.emit("set-mini-view", (view,)) event_system.emit("set-mini-view", (view,))

View File

@@ -12,7 +12,9 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Show Completion") logger.debug("Command: Show Completion")
completer = view.get_completion() completer = view.get_completion()

View File

@@ -13,6 +13,8 @@ from gi.repository import GtkSource
def execute( def execute(
view: GtkSource.View, view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Update Info Bar") logger.debug("Command: Update Info Bar")
file = view.command.get_file(view) file = view.command.get_file(view)

View File

@@ -13,7 +13,9 @@ from gi.repository import Pango
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Zoom In") logger.debug("Command: Zoom In")

View File

@@ -13,7 +13,9 @@ from gi.repository import Pango
def execute( def execute(
view: GtkSource.View = None view: GtkSource.View,
*args,
**kwargs
): ):
logger.debug("Command: Zoom Out") logger.debug("Command: Zoom Out")

View File

@@ -40,10 +40,14 @@ class SourceViewsController(ControllerBase, list):
self.signal_mapper.insert_text(event.file, event.text) self.signal_mapper.insert_text(event.file, event.text)
def _register_command(self, event: Code_Event_Types.RegisterCommandEvent): def _register_command(self, event: Code_Event_Types.RegisterCommandEvent):
if not isinstance(event.binding, list):
event.binding = [ event.binding ]
for binding in event.binding:
self.state_manager.key_mapper.map_command( self.state_manager.key_mapper.map_command(
event.command_name, event.command_name,
{ {
f"{event.binding_mode}": event.binding f"{event.binding_mode}": binding
} }
) )

View File

@@ -22,7 +22,8 @@ class SourceViewsInsertState:
event = Event_Factory.create_event("focused_view", view = source_view) event = Event_Factory.create_event("focused_view", view = source_view)
emit(event) emit(event)
def insert_text(self, file, text): def insert_text(self, file, text: str):
return True return True
def move_cursor(self, source_view, step, count, extend_selection, emit): def move_cursor(self, source_view, step, count, extend_selection, emit):
@@ -51,21 +52,23 @@ class SourceViewsInsertState:
def key_press_event(self, source_view, eve, key_mapper): def key_press_event(self, source_view, eve, key_mapper):
command = key_mapper._key_press_event(eve) command = key_mapper._key_press_event(eve)
is_future = key_mapper._key_release_event(eve) is_future = key_mapper._key_release_event(eve)
char_str = key_mapper.get_char(eve)
if is_future: return True if is_future: return True
if not command: return False if not command: return False
source_view.command.exec(command) source_view.command.exec_with_args(command, source_view, char_str)
return True return True
def key_release_event(self, source_view, eve, key_mapper): def key_release_event(self, source_view, eve, key_mapper):
command = key_mapper._key_release_event(eve) command = key_mapper._key_release_event(eve)
is_past = key_mapper._key_press_event(eve) is_past = key_mapper._key_press_event(eve)
char_str = key_mapper.get_char(eve)
if is_past: return True if is_past: return True
if not command: return False if not command: return False
source_view.command.exec(command) source_view.command.exec_with_args(command, source_view, char_str)
return True return True

View File

@@ -96,19 +96,28 @@ class KeyMapper:
getattr(self.states[state], press_state)[keyname] = command getattr(self.states[state], press_state)[keyname] = command
def _key_press_event(self, eve): def _key_press_event(self, eve):
keyname = Gdk.keyval_name(eve.keyval).lower() keyname = self.get_keyname(eve)
char_str = self.get_char(eve)
self._set_key_state(eve) self._set_key_state(eve)
if keyname in self.states[self.state].held: if keyname in self.states[self.state].held:
return self.states[self.state].held[keyname] return self.states[self.state].held[keyname]
if char_str in self.states[self.state].held:
return self.states[self.state].held[char_str]
def _key_release_event(self, eve): def _key_release_event(self, eve):
keyname = Gdk.keyval_name(eve.keyval).lower() keyname = self.get_keyname(eve)
char_str = self.get_char(eve)
self._set_key_state(eve) self._set_key_state(eve)
if keyname in self.states[self.state].released: if keyname in self.states[self.state].released:
return self.states[self.state].released[keyname] return self.states[self.state].released[keyname]
if char_str in self.states[self.state].released:
return self.states[self.state].released[char_str]
def _set_key_state(self, eve): def _set_key_state(self, eve):
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK) modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
is_control = modifiers & Gdk.ModifierType.CONTROL_MASK is_control = modifiers & Gdk.ModifierType.CONTROL_MASK
@@ -137,3 +146,9 @@ class KeyMapper:
def get_raw_keyname(self, eve): def get_raw_keyname(self, eve):
return Gdk.keyval_name(eve.keyval) return Gdk.keyval_name(eve.keyval)
def get_keyname(self, eve):
return Gdk.keyval_name(eve.keyval).lower()
def get_char(self, eve):
return chr( Gdk.keyval_to_unicode(eve.keyval) )

View File

@@ -33,7 +33,7 @@ class SourceViewDnDMixin:
def _on_uri_data_received(self, uris: []): def _on_uri_data_received(self, uris: []):
uri = uris.pop(0) uri = uris.pop(0)
self.command.exec_with_args("dnd_load_file_to_buffer", (self, uri)) self.command.exec_with_args("dnd_load_file_to_buffer", self, uri)
if not uris: return if not uris: return

View File

@@ -136,7 +136,6 @@ class SourceFile(GtkSource.File):
if self.was_deleted: if self.was_deleted:
self.was_deleted = False self.was_deleted = False
# self.set_path(gfile)
self.set_location( None ) self.set_location( None )
self.set_location( gfile ) self.set_location( gfile )
@@ -148,6 +147,11 @@ class SourceFile(GtkSource.File):
self.set_path(gfile) self.set_path(gfile)
text = gfile.load_bytes()[0].get_data().decode("UTF-8") text = gfile.load_bytes()[0].get_data().decode("UTF-8")
info = gfile.query_info('standard::content-type', Gio.FileQueryInfoFlags.NONE, None)
content_type = info.get_content_type()
self.ftype = Gio.content_type_get_mime_type(content_type)
logger.debug(f"File content type: {self.ftype}")
undo_manager = self.buffer.get_undo_manager() undo_manager = self.buffer.get_undo_manager()
def move_insert_to_start(): def move_insert_to_start():

View File

@@ -31,6 +31,8 @@ class TabWidget(Gtk.Box):
self.set_orientation(0) self.set_orientation(0)
self.set_hexpand(False) self.set_hexpand(False)
self.set_vexpand(False)
self.set_size_request(-1, 12)
def _setup_signals(self): def _setup_signals(self):
... ...

View File

@@ -33,7 +33,9 @@ class ControllerManager(Singleton, dict):
raise ControllerManagerException("Must pass in a 'name' and 'controller'...") raise ControllerManagerException("Must pass in a 'name' and 'controller'...")
if name in self.keys(): if name in self.keys():
raise ControllerManagerException(f"Can't bind controller to registered name of '{name}'...") raise ControllerManagerException(
f"Can't bind controller to existing registered name of '{name}'..."
)
controller.set_controller_context( self._crete_controller_context() ) controller.set_controller_context( self._crete_controller_context() )

View File

@@ -17,4 +17,4 @@ class RegisterCommandEvent(BaseEvent):
command_name: str = "" command_name: str = ""
command: callable = None command: callable = None
binding_mode: str = "" binding_mode: str = ""
binding: str = "" binding: str or list = ""

View File

@@ -3,6 +3,7 @@ import os
import sys import sys
import importlib import importlib
import traceback import traceback
import asyncio
from os.path import join from os.path import join
from os.path import isdir from os.path import isdir
@@ -75,9 +76,14 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
module = self._load_plugin_module(path, folder, target) module = self._load_plugin_module(path, folder, target)
if is_pre_launch: if is_pre_launch:
asyncio.run(
self.execute_plugin(module, manifest_meta) self.execute_plugin(module, manifest_meta)
)
else: else:
GLib.idle_add(self.execute_plugin, module, manifest_meta) GLib.idle_add(
asyncio.run,
self.execute_plugin(module, manifest_meta)
)
except Exception as e: except Exception as e:
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !") logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
logger.debug(f"Trace: {traceback.print_exc()}") logger.debug(f"Trace: {traceback.print_exc()}")
@@ -119,7 +125,7 @@ class PluginsController(ControllerBase, PluginsControllerMixin, PluginReloadMixi
manifest_metas: list = self._manifest_manager.get_post_launch_plugins() manifest_metas: list = self._manifest_manager.get_post_launch_plugins()
self._load_plugins(manifest_metas) self._load_plugins(manifest_metas)
def execute_plugin(self, module: type, manifest_meta: ManifestMeta): async def execute_plugin(self, module: type, manifest_meta: ManifestMeta):
plugin = module.Plugin() plugin = module.Plugin()
plugin.plugin_context: PluginContext = self.create_plugin_context() plugin.plugin_context: PluginContext = self.create_plugin_context()

View File

@@ -111,6 +111,7 @@ scrollbar slider:active {
.tab-label { .tab-label {
margin-left: 2em; margin-left: 2em;
margin-right: 2em; margin-right: 2em;
font-size: 12px;
} }
.tab-close-bttn { .tab-close-bttn {