9 Commits

Author SHA1 Message Date
69e766dc28 feat(editor): introduce split pane manager plugin and refactor source view system
* address TODO item for split_pane_manager plugin and bound keys
* add split_pane_manager plugin with create/close split view commands
* move sibling focus/move commands from core into plugin system
* remove legacy toggle_source_view plugin
* redesign EditorsContainer to use simpler Box-based layout
* refactor source view API to return (scrolled_window, source_view)
* add source view lifecycle events (create/remove/removed)
* rename GetNewCommandSystemEvent → CreateCommandSystemEvent
* update CommandsController to support command system creation and cleanup
* ensure command systems are removed with their source views
* improve focus border handling across sibling chains
* update telescope integration for new source view structure
* clean up container imports and initialization logic
* remove old keybindings and align with new split pane workflow
2026-04-04 21:33:09 -05:00
5911f37449 refactor(command-system): replace legacy CommandSystem with SourceViewCommandSystem
- Remove CommandSystem and CommandSystemMixin
- Introduce SourceViewCommandSystem and shared libs/command_system
- Update CommandsController to use new command system
- Adjust package exports accordingly

feat(lsp): clean up and normalize LSP configurations

- Fix invalid JSON in Godot LSP config (processId -> null, formatting)
- Remove embedded jedi-language-server config from main Python LSP config
- Add separate jedi-lsp-server-config.json

refactor(markers): rename move_word -> move_along_word for clarity

- Update all usages in MarkerManager

chore: minor formatting and whitespace fixes
2026-04-04 14:18:32 -05:00
6e46279da4 refactor(cursor/multi-insert): unify movement logic and improve word navigation
* Extract `_proc_move` to centralize cursor and selection handling
* Rework multi-insert cursor movement and key handling (arrow/ctrl/shift/super)
* Add `ignore_leader` support to decouple leader cursor behavior
* Replace `move_word_snake_case` with improved `move_along_word` (fix '-' handling)
* Add `is_super` modifier support and clean up TODO
2026-03-30 00:36:52 -05:00
d90415bffc Fix: Improve code folding functionality and gutter rendering
- Updated folding actions and engine for more consistent behavior.
- Added helper function `is_fold_hidden()` to check fold visibility state.
- Improved gutter renderer to handle collapsible code blocks more reliably.
- Refined tag handling for invisible folds to prevent desync issues.
- Removed code fold Fix related entry in `TODO.md`.
2026-03-29 14:33:40 -05:00
62a866d9bb feat(tree-sitter, views): initialize AST on focus and emit source view creation event
- Add set_ast helper to centralize Tree-sitter parsing logic
- Parse and attach AST on FocusedViewEvent and TextChangedEvent
- Request file from buffer on view focus before parsing
- Fix parser guard condition in get_parser (handle missing language properly)

- Emit CreatedSourceViewEvent when a new source view is added
- Register CreatedSourceViewEvent in DTO exports

- Update TODO:
  - Remove completed collapsible code blocks task
  - Add fix note for code block icon desync issue

chore:
- Add scaffolding for code_fold UI plugin
- Add created_source_view_event DTO
2026-03-29 03:09:43 -05:00
dc2997ec16 feat(lsp, ui, core): refactor LSP initialization, improve config handling, and clean up TreeSitter
* LSP Client & Manager
  Refactored initialization flow to use internal state instead of passing params
  Added workspace_path and init_opts to LSPClient
  Simplified send_initialize_message() (no external args)
  Updated manager/client APIs to require explicit workspace_path and typed init_opts
  Improved client lifecycle handling and UI button toggling

* LSP Manager UI
  Added {user.home} substitution support in configs
  Use live editor buffer for JSON config parsing instead of static template
  Minor cleanup and formatting improvements

* Core Widgets
  Prevent external modification checks for buffer-only files in SourceFile
  Minor formatting cleanup in SourceView

* Plugins / Manifest
  Expanded manifest schema: added description and copyright, reordered fields

* Cleanup
  Removed TreeSitter compile script and TODO entry
  General code formatting and small consistency fixes

chore: remove unused TreeSitter tooling and related TODO entry
2026-03-28 16:14:04 -05:00
70877a7ee1 feat(event-watchers): rework file state detection and migrate tree-sitter integration
Switch file state checks to trigger on FocusedViewEvent instead of TextChangedEvent
Resolve file via files controller and operate directly on file object
Add user prompt for externally modified files with optional reload
Auto-reload unmodified buffers when external changes are detected
Simplify file_is_deleted / file_is_externally_modified APIs

tree-sitter

Remove bundled tree_sitter_language_pack and related setup/notes scripts
Introduce local lightweight tree_sitter wrapper (Parser, get_parser)
Store parser per file and generate AST directly from buffer text
Remove runtime language download/config logic
Update manifest (drop autoload)

core

Simplify CLI file handling in BaseControllerMixin
Send directory args directly via IPC instead of encoding in file list

cleanup

Remove unused libs, scripts, and legacy code paths
2026-03-27 20:00:59 -05:00
cb73f6b3b0 Fix unload lifecycle, widget cleanup, and plugin removal handling
- Rename GodotHandler to GDScriptHandler in LSP client
- Fix unload() method naming in nanoesq_temp_buffer plugin
- Return manifest_meta in manifest_manager for manual launch plugins
- Properly destroy tabs_widget, viewport, and scrolled_win on unload
- Refactor plugin removal to search all manifest lists and fix cleanup order
- Fix widget reference in plugins_ui (use child instead of box)
2026-03-23 23:05:20 -05:00
e6eaa1d83c Fixed file_history plugin after breakage from testing a pattern 2026-03-23 21:42:05 -05:00
81 changed files with 1404 additions and 1065 deletions

View File

@@ -1,10 +1,7 @@
___ ___
### Add ### Add
1. Add Godot LSP Client 1. Add Godot LSP Client
1. Add TreeSitter
1. Add Collapsable code blocks
1. Add Terminal plugin 1. Add Terminal plugin
1. Add Plugin to <Shift\><Ctrl\>| and <Ctrl\>| to split views up, down, left, right
1. Add <Ctrl\>i to **lsp_manager** to list who implements xyz 1. Add <Ctrl\>i to **lsp_manager** to list who implements xyz
___ ___
@@ -14,6 +11,8 @@ ___
___ ___
### Fix ### Fix
- Fix <Ctrl\>z in multi-insert mode being funky. Insure updates happen on block level.
I.E, maybe push updates to queue to insure block undo/redo?
- Fix on lsp client unload to close files lsp side and unload server endpoint - Fix on lsp client unload to close files lsp side and unload server endpoint
___ ___

View File

@@ -25,7 +25,7 @@ class Plugin(PluginCode):
if len(history) == history_size: if len(history) == history_size:
history.pop(0) history.pop(0)
history.append(event.file) history.append(event.file.fpath)
def load(self): def load(self):
self._manage_signals("register_command") self._manage_signals("register_command")
@@ -60,6 +60,6 @@ class Handler:
view._on_uri_data_received( view._on_uri_data_received(
[ [
history.pop().replace("file://", "") f"file://{history.pop()}"
] ]
) )

View File

@@ -23,7 +23,7 @@ class Plugin(PluginCode):
def load(self): def load(self):
self._manage_signals("register_command") self._manage_signals("register_command")
def load(self): def unload(self):
self._manage_signals("unregister_command") self._manage_signals("unregister_command")
def _manage_signals(self, action: str): def _manage_signals(self, action: str):

View File

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

View File

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

View File

@@ -0,0 +1,64 @@
# 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
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
emit_to: callable = None
def execute(
source_view,
char_str,
modkeys_states
):
logger.debug("Command: Close Split Pane")
scrolled_win = source_view.get_parent()
pane = scrolled_win.get_parent()
if not isinstance(pane, Gtk.Paned): return
container = pane.get_parent()
source_view1 = pane.get_child1()
source_view2 = pane.get_child2()
if scrolled_win == source_view1:
remaining = source_view2
closing_view = source_view
else:
remaining = source_view1
closing_view = source_view
remaining_view = remaining.get_child()
left = closing_view.sibling_left
right = closing_view.sibling_right
if left:
left.sibling_right = right
if right:
right.sibling_left = left
pane.remove(source_view1)
pane.remove(source_view2)
container.remove(pane)
container.add(remaining)
event = Event_Factory.create_event(
"remove_source_view",
view = closing_view
)
emit_to("source_views", event)
remaining_view.grab_focus()

View File

@@ -0,0 +1,66 @@
# Python imports
# Lib imports
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from libs.dto.states import SourceViewStates
emit_to: callable = None
def execute(
source_view1,
char_str,
modkeys_states
):
logger.debug("Command: Split Pane")
scrolled_win1 = source_view1.get_parent()
container = scrolled_win1.get_parent()
pane = Gtk.Paned()
event = Event_Factory.create_event(
"create_source_view",
state = SourceViewStates.INSERT
)
emit_to("source_views", event)
scrolled_win2, \
source_view2 = event.response
old_sibling_right = None
if source_view1.sibling_right:
old_sibling_right = source_view1.sibling_right
source_view1.sibling_right = source_view2
if old_sibling_right:
old_sibling_right.sibling_left = source_view2
source_view2.sibling_right = old_sibling_right
source_view2.sibling_left = source_view1
pane.set_hexpand(True)
pane.set_vexpand(True)
pane.set_wide_handle(True)
container.remove(scrolled_win1)
pane.pack1( scrolled_win1, True, True )
pane.pack2( scrolled_win2, True, True )
container.add(pane)
pane.show_all()
is_control, is_shift, is_alt = modkeys_states
if is_control and is_shift:
pane.set_orientation(Gtk.Orientation.VERTICAL)
elif is_control:
pane.set_orientation(Gtk.Orientation.HORIZONTAL)
source_view2.grab_focus()
source_view2.command.exec("new_file")

View File

@@ -7,6 +7,7 @@ gi.require_version('GtkSource', '4')
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
@@ -18,5 +19,4 @@ def execute(
): ):
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.sibling_left.get_parent().show()
view.sibling_left.grab_focus() view.sibling_left.grab_focus()

View File

@@ -18,5 +18,4 @@ def execute(
): ):
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.sibling_right.get_parent().show()
view.sibling_right.grab_focus() view.sibling_right.grab_focus()

View File

@@ -1,5 +1,5 @@
{ {
"name": "Toggle Source View", "name": "Split Pane Manager",
"author": "ITDominator", "author": "ITDominator",
"version": "0.0.1", "version": "0.0.1",
"support": "", "support": "",

View File

@@ -0,0 +1,100 @@
# Python imports
# Lib imports
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
# Application imports
from plugins.plugin_types import PluginCode
from libs.event_factory import Event_Factory, Code_Event_Types
from . import create_split_view, \
close_split_view, \
focus_left_sibling, \
focus_right_sibling, \
move_to_left_sibling, \
move_to_right_sibling
class Plugin(PluginCode):
def __init__(self):
super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent):
...
def load(self):
gemit_to = self.emit_to
self._manage_signals("register_command")
def unload(self):
self._manage_signals("unregister_command")
def _manage_signals(self, action: str):
_create_split_view = create_split_view
_close_split_view = close_split_view
_create_split_view.emit_to = self.emit_to
_close_split_view.emit_to = self.emit_to
event = Event_Factory.create_event(action,
command_name = "create_split_view",
command = _create_split_view,
binding_mode = "released",
binding = ["<Control>\\", "<Shift><Control>|"]
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "close_split_view",
command = _close_split_view,
binding_mode = "released",
binding = "<Shift><Control>w"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "focus_left_sibling",
command = focus_left_sibling,
binding_mode = "released",
binding = "<Control>Page_Up"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "focus_right_sibling",
command = focus_right_sibling,
binding_mode = "released",
binding = "<Control>Page_Down"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "move_to_left_sibling",
command = move_to_left_sibling,
binding_mode = "released",
binding = "<Control><Shift>Up"
)
self.emit_to("source_views", event)
event = Event_Factory.create_event(action,
command_name = "move_to_right_sibling",
command = move_to_right_sibling,
binding_mode = "released",
binding = "<Control><Shift>Down"
)
self.emit_to("source_views", event)
def run(self):
...

View File

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

View File

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

View File

@@ -1,64 +0,0 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
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 = "toggle_source_view",
command = Handler,
binding_mode = "released",
binding = "<Shift><Control>h"
)
self.emit_to("source_views", event)
def unload(self):
event = Event_Factory.create_event("unregister_command",
command_name = "toggle_source_view",
command = Handler,
binding_mode = "released",
binding = "<Shift><Control>h"
)
self.emit_to("source_views", event)
def run(self):
...
class Handler:
@staticmethod
def execute(
view: any,
char_str: str,
*args,
**kwargs
):
logger.debug("Command: Toggle Source View")
target = view.get_parent()
target.hide() if target.is_visible() else target.show()
if view.sibling_left:
target = view.sibling_left.get_parent()
target.show()
view.sibling_left.grab_focus()
if view.sibling_right:
target = view.sibling_right.get_parent()
target.show()
view.sibling_right.grab_focus()

View File

@@ -17,13 +17,22 @@ class Plugin(PluginCode):
def _controller_message(self, event: Code_Event_Types.CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.TextChangedEvent): if not isinstance(event, Code_Event_Types.FocusedViewEvent): return
event.file.check_file_on_disk() event = Event_Factory.create_event(
"get_file", buffer = event.view.get_buffer()
)
self.emit_to("files", event)
if event.file.is_deleted(): file = event.response
file_is_deleted(event, self.emit) if not file: return
elif event.file.is_externally_modified(): if file.ftype == "buffer": return
file_is_externally_modified(event, self.emit)
file.check_file_on_disk()
if file.is_deleted():
file_is_deleted(file, self.emit)
elif file.is_externally_modified():
file_is_externally_modified(file, self.emit)
def load(self): def load(self):
... ...

View File

@@ -10,23 +10,45 @@ from libs.event_factory import Event_Factory, Code_Event_Types
def file_is_deleted(event, emit): def ask_yes_no(message):
event.file.was_deleted = True dialog = Gtk.MessageDialog(
parent = None,
flags = 0,
message_type = Gtk.MessageType.QUESTION,
buttons = Gtk.ButtonsType.YES_NO,
text = message,
)
dialog.set_title("Confirm")
response = dialog.run()
dialog.destroy()
return response == Gtk.ResponseType.YES
def file_is_deleted(file, emit):
file.was_deleted = True
event = Event_Factory.create_event( event = Event_Factory.create_event(
"file_externally_deleted", "file_externally_deleted",
file = event.file, file = file,
buffer = event.buffer buffer = file.buffer
) )
emit(event) emit(event)
def file_is_externally_modified(event, emit): def file_is_externally_modified(file, emit):
# event = Event_Factory.create_event( event = Event_Factory.create_event(
# "file_externally_modified", "file_externally_modified",
# file = event.file, file = file,
# buffer = event.buffer buffer = file.buffer
# ) )
# emit(event) emit(event)
... if not file.buffer.get_modified():
file.reload()
return
result = ask_yes_no("File has been externally modified. Reload?")
if not result: return
file.reload()

View File

@@ -1,39 +0,0 @@
from .libs.tree_sitter_language_pack import \
init, download, get_language, get_parser, available_languages, process, ProcessConfig
# Optional: Pre-download specific languages for offline use
#init(["python", "javascript", "rust"])
# Get a language (auto-downloads if not cached)
language = get_language("python")
# Get a pre-configured parser (auto-downloads if needed)
parser = get_parser("python")
tree = parser.parse(b"def hello(): pass")
print(tree.root_node)
# List all available languages
for lang in available_languages():
print(lang)
# Extract file intelligence (auto-downloads language if needed)
result = process(
"def hello(): pass",
ProcessConfig( language = "python")
)
print(f"Functions: {len(result['structure'])}")
# Pre-download languages for offline use
download(["python", "javascript"])
# With chunking
result = process(
source,
ProcessConfig(
language = "python",
chunk_max_size = 1000,
comments = True
)
)
print(f"Chunks: {len(result['chunks'])}")

View File

@@ -0,0 +1,134 @@
#!/bin/bash
# . CONFIG.sh
# set -o xtrace ## To debug scripts
# set -o errexit ## To exit on error
# set -o errunset ## To exit if a variable is referenced but not set
#!/usr/bin/env bash
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TOOLS="$ROOT/.tools"
NODE_DIR="$TOOLS/node"
GRAMMARS_DIR="$ROOT/grammars"
BUILD_DIR="$ROOT/build"
OUTPUT="$ROOT/compiled"
NODE_VERSION="v24.14.1"
NODE_DIST="node-$NODE_VERSION-linux-x64"
NODE_ARCHIVE="$TOOLS/node.tar.xz"
NODE_URL="https://nodejs.org/dist/$NODE_VERSION/$NODE_DIST.tar.xz"
TS_CLI_VERSION="0.22.6"
LANGS=(
tree-sitter-python
tree-sitter-javascript
tree-sitter-html
tree-sitter-css
tree-sitter-json
tree-sitter-java
tree-sitter-c
tree-sitter-cpp
tree-sitter-go
)
REPOS=(
https://github.com/tree-sitter/tree-sitter-python
https://github.com/tree-sitter/tree-sitter-javascript
https://github.com/tree-sitter/tree-sitter-html
https://github.com/tree-sitter/tree-sitter-css
https://github.com/tree-sitter/tree-sitter-json
https://github.com/tree-sitter/tree-sitter-java
https://github.com/tree-sitter/tree-sitter-c
https://github.com/tree-sitter/tree-sitter-cpp
https://github.com/tree-sitter/tree-sitter-go
)
mkdir -p "$TOOLS" "$GRAMMARS_DIR" "$BUILD_DIR" "$OUTPUT"
ensure_node() {
if [ -x "$NODE_DIR/bin/node" ]; then
echo "==> Using cached Node.js"
return
fi
echo "==> Downloading Node.js $NODE_VERSION"
wget -O "$NODE_ARCHIVE" "$NODE_URL"
echo "==> Extracting Node.js"
mkdir -p "$NODE_DIR"
tar -xf "$NODE_ARCHIVE" -C "$NODE_DIR" --strip-components=1
rm "$NODE_ARCHIVE"
echo "==> Node installed at $NODE_DIR"
}
ensure_tree_sitter() {
export PATH="$NODE_DIR/bin:$PATH"
TS="$TOOLS/node_modules/.bin/tree-sitter"
if [ -x "$TS" ]; then
echo "==> Using cached tree-sitter-cli"
return
fi
echo "==> Installing tree-sitter-cli"
cd "$TOOLS"
npm init -y >/dev/null 2>&1 || true
npm install tree-sitter-cli@$TS_CLI_VERSION
}
sync_grammars() {
echo "==> Syncing grammars"
for i in "${!LANGS[@]}"; do
NAME="${LANGS[$i]}"
REPO="${REPOS[$i]}"
TARGET="$GRAMMARS_DIR/$NAME"
if [ -d "$TARGET/.git" ]; then
echo "Updating $NAME"
git -C "$TARGET" pull --depth 1
else
echo "Cloning $NAME"
git clone --depth 1 "$REPO" "$TARGET"
fi
done
}
build_lib() {
echo "==> Building Tree-sitter library"
mkdir -p "$OUTPUT"
PARSER_SRC=()
INCLUDE_PATHS=()
for GRAMMAR in "${LANGS[@]/#/$GRAMMARS_DIR/}"; do
echo "==> Processing grammar $GRAMMAR"
PARSER_SRC+=("$GRAMMAR/src/parser.c")
if [[ -f "$GRAMMAR/src/scanner.c" ]]; then
PARSER_SRC+=("$GRAMMAR/src/scanner.c")
fi
INCLUDE_PATHS+=("-I$GRAMMAR/src")
done
gcc -shared -o "$OUTPUT/languages.so" "${PARSER_SRC[@]}" "${INCLUDE_PATHS[@]}" -fPIC
echo "==> Output: $OUTPUT/languages.so"
}
main() {
ensure_node
ensure_tree_sitter
sync_grammars
build_lib
}
main "$@"

View File

@@ -1,54 +0,0 @@
from typing import TypeAlias
from tree_sitter_language_pack._native import (
DownloadError,
LanguageNotFoundError,
ParseError,
ProcessConfig,
QueryError,
TreeHandle,
available_languages,
cache_dir,
clean_cache,
configure,
download,
download_all,
downloaded_languages,
get_binding,
get_language,
get_parser,
has_language,
init,
language_count,
manifest_languages,
parse_string,
process,
)
SupportedLanguage: TypeAlias = str
__all__ = [
"DownloadError",
"LanguageNotFoundError",
"ParseError",
"ProcessConfig",
"QueryError",
"SupportedLanguage",
"TreeHandle",
"available_languages",
"cache_dir",
"clean_cache",
"configure",
"download",
"download_all",
"downloaded_languages",
"get_binding",
"get_language",
"get_parser",
"has_language",
"init",
"language_count",
"manifest_languages",
"parse_string",
"process",
]

View File

@@ -1,276 +0,0 @@
from typing import Literal, TypeAlias
from tree_sitter import Language, Parser
class LanguageNotFoundError(ValueError): ...
class DownloadError(RuntimeError): ...
SupportedLanguage: TypeAlias = Literal[
"actionscript",
"ada",
"agda",
"apex",
"arduino",
"asm",
"astro",
"bash",
"batch",
"bazel",
"beancount",
"bibtex",
"bicep",
"bitbake",
"bsl",
"c",
"cairo",
"capnp",
"chatito",
"clarity",
"clojure",
"cmake",
"cobol",
"comment",
"commonlisp",
"cpon",
"cpp",
"css",
"csv",
"cuda",
"d",
"dart",
"diff",
"dockerfile",
"doxygen",
"dtd",
"elisp",
"elixir",
"elm",
"erlang",
"fennel",
"firrtl",
"fish",
"fortran",
"fsharp",
"fsharp_signature",
"func",
"gdscript",
"gitattributes",
"gitcommit",
"gitignore",
"gleam",
"glsl",
"gn",
"go",
"gomod",
"gosum",
"gradle",
"graphql",
"groovy",
"gstlaunch",
"hack",
"hare",
"haskell",
"haxe",
"hcl",
"heex",
"hlsl",
"html",
"hyprlang",
"ignorefile",
"ini",
"ispc",
"janet",
"java",
"javascript",
"jsdoc",
"json",
"jsonnet",
"julia",
"kconfig",
"kdl",
"kotlin",
"latex",
"linkerscript",
"lisp",
"llvm",
"lua",
"luadoc",
"luap",
"luau",
"magik",
"make",
"makefile",
"markdown",
"markdown_inline",
"matlab",
"mermaid",
"meson",
"netlinx",
"nim",
"ninja",
"nix",
"nqc",
"objc",
"ocaml",
"ocaml_interface",
"odin",
"org",
"pascal",
"pem",
"perl",
"pgn",
"php",
"pkl",
"po",
"pony",
"powershell",
"printf",
"prisma",
"properties",
"proto",
"psv",
"puppet",
"purescript",
"pymanifest",
"python",
"qmldir",
"qmljs",
"query",
"r",
"racket",
"re2c",
"readline",
"rego",
"requirements",
"ron",
"rst",
"ruby",
"rust",
"scala",
"scheme",
"scss",
"shell",
"smali",
"smithy",
"solidity",
"sparql",
"sql",
"squirrel",
"starlark",
"svelte",
"swift",
"tablegen",
"tcl",
"terraform",
"test",
"thrift",
"toml",
"tsv",
"tsx",
"twig",
"typescript",
"typst",
"udev",
"ungrammar",
"uxntal",
"v",
"verilog",
"vhdl",
"vim",
"vue",
"wast",
"wat",
"wgsl",
"xcompose",
"xml",
"yuck",
"zig",
]
class ParseError(RuntimeError): ...
class QueryError(ValueError): ...
class ProcessConfig:
language: str
structure: bool
imports: bool
exports: bool
comments: bool
docstrings: bool
symbols: bool
diagnostics: bool
chunk_max_size: int | None
def __init__(
self,
language: str,
*,
structure: bool = True,
imports: bool = True,
exports: bool = True,
comments: bool = True,
docstrings: bool = True,
symbols: bool = True,
diagnostics: bool = True,
chunk_max_size: int | None = None,
) -> None: ...
@staticmethod
def all(language: str) -> ProcessConfig: ...
@staticmethod
def minimal(language: str) -> ProcessConfig: ...
class TreeHandle:
def root_node_type(self) -> str: ...
def root_child_count(self) -> int: ...
def contains_node_type(self, node_type: str) -> bool: ...
def has_error_nodes(self) -> bool: ...
def to_sexp(self) -> str: ...
def error_count(self) -> int: ...
def root_node_info(self) -> dict[str, object]: ...
def find_nodes_by_type(self, node_type: str) -> list[dict[str, object]]: ...
def named_children_info(self) -> list[dict[str, object]]: ...
def extract_text(self, start_byte: int, end_byte: int) -> str: ...
def run_query(self, language: str, query_source: str) -> list[dict[str, object]]: ...
__all__ = [
"DownloadError",
"LanguageNotFoundError",
"ParseError",
"ProcessConfig",
"QueryError",
"SupportedLanguage",
"TreeHandle",
"available_languages",
"cache_dir",
"clean_cache",
"configure",
"download",
"download_all",
"downloaded_languages",
"get_binding",
"get_language",
"get_parser",
"has_language",
"init",
"language_count",
"manifest_languages",
"parse_string",
"process",
]
def get_binding(name: SupportedLanguage) -> object: ...
def get_language(name: SupportedLanguage) -> Language: ...
def get_parser(name: SupportedLanguage) -> Parser: ...
def available_languages() -> list[str]: ...
def has_language(name: str) -> bool: ...
def language_count() -> int: ...
def parse_string(language: str, source: str) -> TreeHandle: ...
def process(source: str, config: ProcessConfig) -> dict[str, object]: ...
def init(config: dict[str, object]) -> None: ...
def configure(*, cache_dir: str | None = None) -> None: ...
def download(names: list[str]) -> int: ...
def download_all() -> int: ...
def manifest_languages() -> list[str]: ...
def downloaded_languages() -> list[str]: ...
def clean_cache() -> None: ...
def cache_dir() -> str: ...

View File

@@ -4,6 +4,5 @@
"version": "0.0.1", "version": "0.0.1",
"support": "", "support": "",
"pre_launch": true, "pre_launch": true,
"autoload": false,
"requests": {} "requests": {}
} }

View File

@@ -1,17 +0,0 @@
from .tree_sitter import Language
Language.build_library(
"my-languages.so",
[
"tree-sitter-python",
"tree-sitter-javascript",
"tree-sitter-html",
"tree-sitter-css",
"tree-sitter-json",
"tree-sitter-java",
"tree-sitter-c",
"tree-sitter-cpp",
"tree-sitter-go",
"tree-sitter-gdscript",
],
)

View File

@@ -1,121 +0,0 @@
#!/bin/bash
# . CONFIG.sh
# set -o xtrace ## To debug scripts
# set -o errexit ## To exit on error
# set -o errunset ## To exit if a variable is referenced but not set
function old_build() {
touch __init__.py
cat <<'EOF' > compile.py
from tree_sitter import Language
Language.build_library(
"my-languages.so",
[
"tree-sitter-python",
"tree-sitter-javascript",
"tree-sitter-html",
"tree-sitter-css",
"tree-sitter-json",
"tree-sitter-java",
"tree-sitter-c",
"tree-sitter-cpp",
"tree-sitter-go"
"tree-sitter-gdscript",
],
)
EOF
python compile.py
cd ..
}
function build() {
touch __init__.py
cat <<'EOF' > compile.py
from tree_sitter_language_pack import init, download, get_language, get_parser, available_languages
from tree_sitter_language_pack import process, ProcessConfig
# Optional: Pre-download specific languages for offline use
# init(["python", "javascript", "rust"])
# Get a language (auto-downloads if not cached)
language = get_language("python")
# Get a pre-configured parser (auto-downloads if needed)
parser = get_parser("python")
tree = parser.parse(b"def hello(): pass")
print(tree.root_node)
# List all available languages
for lang in available_languages():
print(lang)
# Extract file intelligence (auto-downloads language if needed)
result = process(
"def hello(): pass",
ProcessConfig(
language = "python"
)
)
print(f"Functions: {len(result['structure'])}")
# Pre-download languages for offline use
download(["python", "javascript"])
# With chunking
result = process(
source,
ProcessConfig(
language = "python",
chunk_max_size = 1000,
comments = True
)
)
print(f"Chunks: {len(result['chunks'])}")
EOF
python compile.py
cd ..
}
function clone() {
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-python
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-javascript
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-html
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-css
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-json
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-java
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-c
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-cpp
git clone --depth 1 https://github.com/tree-sitter/tree-sitter-go
# git clone --depth 1 https://github.com/godotengine/tree-sitter-gdscript
}
function setup() {
# pip install tree-sitter -t .
pip install tree-sitter-language-pack -t .
# pip install tree_sitter_languages -t . # Old unmaintained library
}
function main() {
cd "$(dirname "$0")"
echo "Working Dir: " $(pwd)
mkdir -p build
cd build
# clone "$@"
setup "$@"
build "$@"
}
main "$@";

View File

@@ -1,14 +1,4 @@
# Python imports # Python imports
import sys
import os
BASE_DIR = os.path.dirname( os.path.realpath(__file__) )
LIBS_DIR = f"{BASE_DIR}/libs"
if str(LIBS_DIR) not in sys.path:
sys.path.insert(0, LIBS_DIR)
# Lib imports # Lib imports
@@ -17,7 +7,7 @@ from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode from plugins.plugin_types import PluginCode
import tree_sitter_language_pack as tslp from .tree_sitter import Parser, get_parser
@@ -26,52 +16,48 @@ class Plugin(PluginCode):
super(Plugin, self).__init__() super(Plugin, self).__init__()
def _controller_message(self, event: Code_Event_Types.CodeEvent): def set_ast(self, file):
if isinstance(event, Code_Event_Types.TextChangedEvent): if not hasattr(file, "tree_sitter"):
if not tslp.has_language( event.file.ftype ): parser = get_parser( file.ftype )
try: if not parser: return
tslp.download( [event.file.ftype] )
except Exception:
logger.info(
f"Tree Sitter Language Pack:\nCouldn't download -> '{event.file.ftype}' language type..."
)
return
buffer = event.file.buffer file.tree_sitter = parser
buffer = file.buffer
start_itr, \ start_itr, \
end_itr = buffer.get_bounds() end_itr = buffer.get_bounds()
text = buffer.get_text(start_itr, end_itr, True) text = buffer.get_text(start_itr, end_itr, True)
result = tslp.process(
text,
tslp.ProcessConfig(
language = event.file.ftype
)
)
event.file.tree_sitter_meta = result tree = file.tree_sitter.parse( text.encode("UTF-8") )
file.ast = tree
def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.FocusedViewEvent):
self.view = event.view
event = Event_Factory.create_event(
"get_file", buffer = self.view.get_buffer()
)
self.emit_to("files", event)
file = event.response
if not file: return
if file.ftype == "buffer": return
self.set_ast(file)
elif isinstance(event, Code_Event_Types.TextChangedEvent):
self.set_ast(event.file)
# root = tree.root_node
# print("Root type:", root.type)
# for child in root.children:
# print(child.type, child.start_point, child.end_point)
def load(self): def load(self):
tslp.configure( ...
cache_dir = f"{BASE_DIR}/cache/tree-sitter-language-pack/v1.0.0/libs"
)
def unload(self): def unload(self):
... ...
def run(self): def run(self):
# tslp.download(
# [
# "python",
# "java",
# "go",
# "c",
# "cpp",
# "javascript",
# "html",
# "css",
# "json"
# ]
# )
# "gdscript"
... ...

View File

@@ -0,0 +1,57 @@
# Python imports
import os
import ctypes
# Lib imports
# Application imports
from .libs.tree_sitter import Language, Parser
_LIB_PATH = os.path.join(
os.path.dirname(__file__), "languages", "languages.so"
)
_LANGUAGE_LIB = ctypes.CDLL(_LIB_PATH)
def load_language(name: str) -> Language | None:
symbol = f"tree_sitter_{name}"
try:
func = getattr(_LANGUAGE_LIB, symbol)
except AttributeError:
logger.warning(f"Tree-sitter: {name} not found in 'languages.so' shared library...")
return None
func.restype = ctypes.c_void_p
return Language(func())
LANGUAGES = {
"python": load_language("python"),
"python3": load_language("python"),
"javascript": load_language("javascript"),
"html": load_language("html"),
"css": load_language("css"),
"json": load_language("json"),
"java": load_language("java"),
"c": load_language("c"),
"cpp": load_language("cpp"),
"go": load_language("go")
}
def get_parser(lang_name: str) -> Parser | None:
if not lang_name in LANGUAGES: return
language = LANGUAGES[lang_name]
if not language: return
parser = Parser()
parser.language = language
return parser

View File

@@ -7,7 +7,7 @@
"socket": "ws://127.0.0.1:9999/gdscript", "socket": "ws://127.0.0.1:9999/gdscript",
"socket-two": "ws://127.0.0.1:9999/?name=gdscript", "socket-two": "ws://127.0.0.1:9999/?name=gdscript",
"initialization-options": { "initialization-options": {
"processId": , "processId": null,
"clientInfo": { "clientInfo": {
"name": "Godot", "name": "Godot",
"version": "4.4" "version": "4.4"

View File

@@ -11,7 +11,7 @@ from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode from plugins.plugin_types import PluginCode
from .response_handler import GodotHandler from .response_handler import GDScriptHandler
@@ -30,7 +30,7 @@ class Plugin(PluginCode):
event = Event_Factory.create_event("register_lsp_client", event = Event_Factory.create_event("register_lsp_client",
lang_id = "gdscript", lang_id = "gdscript",
lang_config = config, lang_config = config,
handler = GodotHandler handler = GDScriptHandler
) )
self.emit_to("lsp_manager", event) self.emit_to("lsp_manager", event)

View File

@@ -1 +1 @@
from .python import PythonHandler from .gdscript import GDScriptHandler

View File

@@ -7,6 +7,6 @@ from lsp_manager.response_handlers.default import DefaultHandler
class GodotHandler(DefaultHandler): class GDScriptHandler(DefaultHandler):
"""Uses default handling, can override if Godot needs special logic.""" """Uses default handling, can override if Godot needs special logic."""
... ...

View File

@@ -19,13 +19,10 @@ class LSPClient(LSPClientWebsocket):
def __init__(self): def __init__(self):
super(LSPClient, self).__init__() super(LSPClient, self).__init__()
# https://github.com/microsoft/multilspy/tree/main/src/multilspy/language_servers
# initialize-params-slim.json was created off of jedi_language_server one
# self._init_params = settings_manager.get_lsp_init_data()
self._language: str = "" self._language: str = ""
self._workspace_path: str = ""
self._init_params: dict = {} self._init_params: dict = {}
self._event_history: dict[int, str] = {} self._init_opts: dict = {}
try: try:
_USER_HOME = path.expanduser('~') _USER_HOME = path.expanduser('~')
@@ -33,19 +30,29 @@ class LSPClient(LSPClientWebsocket):
_LSP_INIT_CONFIG = f"{_SCRIPT_PTH}/../configs/initialize-params-slim.json" _LSP_INIT_CONFIG = f"{_SCRIPT_PTH}/../configs/initialize-params-slim.json"
with open(_LSP_INIT_CONFIG) as file: with open(_LSP_INIT_CONFIG) as file:
data = file.read().replace("{user.home}", _USER_HOME) data = file.read()
self._init_params = json.loads(data) self._init_params = json.loads(data)
except Exception as e: except Exception as e:
logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" ) logger.error( f"LSP Controller: {_LSP_INIT_CONFIG}\n\t\t{repr(e)}" )
self._message_id: int = -1
self._socket = None self._socket = None
self._message_id: int = -1
self._event_history: dict[int, str] = {}
self.read_lock = threading.Lock() self.read_lock = threading.Lock()
self.write_lock = threading.Lock() self.write_lock = threading.Lock()
def set_language(self, language: str): def set_language(self, language: str):
self._language = language self._language = language
def set_workspace_path(self, workspace_path: str):
self._workspace_path = workspace_path
def set_init_opts(self, init_opts: dict[str, str]):
self._init_opts = init_opts
def set_socket(self, socket: str): def set_socket(self, socket: str):
self._socket = socket self._socket = socket

View File

@@ -17,11 +17,12 @@ from ..dto.code.lsp.lsp_messages import symbols_request
class LSPClientEvents: class LSPClientEvents:
def send_initialize_message(self, init_ops: dict, workspace_file: str, workspace_uri: str): def send_initialize_message(self):
folder_name = os.path.basename(workspace_file) folder_name = os.path.basename(self._workspace_path)
workspace_uri = f"file://{self._workspace_path}"
self._init_params["processId"] = None self._init_params["processId"] = None
self._init_params["rootPath"] = workspace_file self._init_params["rootPath"] = self._workspace_path
self._init_params["rootUri"] = workspace_uri self._init_params["rootUri"] = workspace_uri
self._init_params["workspaceFolders"] = [ self._init_params["workspaceFolders"] = [
{ {
@@ -30,7 +31,7 @@ class LSPClientEvents:
} }
] ]
self._init_params["initializationOptions"] = init_ops self._init_params["initializationOptions"] = self._init_opts
self.send_request("initialize", self._init_params) self.send_request("initialize", self._init_params)
def send_initialized_message(self): def send_initialized_message(self):

View File

@@ -54,17 +54,21 @@ class LSPManager(ControllerBase):
self.response_registry.unregister_handler(event.lang_id) self.response_registry.unregister_handler(event.lang_id)
self.lsp_manager_ui.remove_client_listing(event.lang_id) self.lsp_manager_ui.remove_client_listing(event.lang_id)
def _on_create_client(self, ui, lang_id: str, workspace_uri: str) -> bool: def _on_create_client(self, ui, lang_id: str, workspace_path: str) -> bool:
init_opts = ui.get_init_opts(lang_id) init_opts = ui.get_init_opts(lang_id)
result = self.create_client(lang_id, workspace_uri, init_opts) result = self.create_client(lang_id, workspace_path, init_opts)
if result: if result:
ui.toggle_client_buttons(show_close=True) ui.toggle_client_buttons(show_close = True)
return result return result
def _on_close_client(self, ui, lang_id: str) -> bool: def _on_close_client(self, ui, lang_id: str) -> bool:
result = self.close_client(lang_id) result = self.close_client(lang_id)
if result: if result:
ui.toggle_client_buttons(show_close=False) ui.toggle_client_buttons(show_close = False)
return result return result
def handle_destroy(self): def handle_destroy(self):
@@ -73,12 +77,12 @@ class LSPManager(ControllerBase):
def create_client( def create_client(
self, self,
lang_id: str = "python", lang_id: str,
workspace_uri: str = "", workspace_path: str,
init_opts: dict = {} init_opts: dict[str, str]
) -> bool: ) -> bool:
client = self.lsp_manager_client.create_client( client = self.lsp_manager_client.create_client(
lang_id, workspace_uri, init_opts lang_id, workspace_path, init_opts
) )
handler = self.response_registry.get_handler(lang_id) handler = self.response_registry.get_handler(lang_id)
self.lsp_manager_client.active_language_id = lang_id self.lsp_manager_client.active_language_id = lang_id
@@ -92,7 +96,7 @@ class LSPManager(ControllerBase):
handler.set_response_cache(self.response_cache) handler.set_response_cache(self.response_cache)
client.handle_lsp_response = self.server_response client.handle_lsp_response = self.server_response
client.send_initialize_message(init_opts, "", f"file://{workspace_uri}") client.send_initialize_message()
return True return True

View File

@@ -22,9 +22,9 @@ class LSPManagerClient(LSPClientEventsMixin):
def create_client( def create_client(
self, self,
lang_id: str = "python", lang_id: str,
workspace_uri: str = "", workspace_path: str,
init_opts: dict = {} init_opts: dict[str, str]
) -> LSPClient: ) -> LSPClient:
if lang_id in self.clients: return None if lang_id in self.clients: return None
@@ -33,8 +33,10 @@ class LSPManagerClient(LSPClientEventsMixin):
uri = f"ws://{address}:{port}/{lang_id}" uri = f"ws://{address}:{port}/{lang_id}"
client = LSPClient() client = LSPClient()
client.set_language(lang_id)
client.set_socket(uri) client.set_socket(uri)
client.set_language(lang_id)
client.set_workspace_path(workspace_path)
client.set_init_opts(init_opts)
client.start_client() client.start_client()
if not client.ws_client.wait_for_connection(timeout = 5.0): if not client.ws_client.wait_for_connection(timeout = 5.0):

View File

@@ -1,4 +1,5 @@
# Python imports # Python imports
from os import path
import json import json
# Lib imports # Lib imports
@@ -24,6 +25,8 @@ class LSPManagerUI(Gtk.Dialog):
def __init__(self): def __init__(self):
super(LSPManagerUI, self).__init__() super(LSPManagerUI, self).__init__()
self._USER_HOME = path.expanduser('~')
self.client_configs: dict[str, str] = {} self.client_configs: dict[str, str] = {}
self.source_view = None self.source_view = None
@@ -167,9 +170,11 @@ class LSPManagerUI(Gtk.Dialog):
lang_id = self.combo_box.get_active_text() lang_id = self.combo_box.get_active_text()
if not lang_id: return if not lang_id: return
json_str = self.client_configs[lang_id].replace("{workspace.folder}", workspace_dir) json_str = self.client_configs[lang_id] \
buffer = self.source_view.get_buffer() .replace("{workspace.folder}", workspace_dir) \
.replace("{user.home}", self._USER_HOME)
buffer = self.source_view.get_buffer()
buffer.set_text(json_str, -1) buffer.set_text(json_str, -1)
def map_parent_resize_event(self, parent): def map_parent_resize_event(self, parent):
@@ -204,7 +209,7 @@ class LSPManagerUI(Gtk.Dialog):
model = self.combo_box.get_model() model = self.combo_box.get_model()
for i, row in enumerate(model): for i, row in enumerate(model):
if row[0] == lang_id: # assuming text is in column 0 if row[0] == lang_id:
self.combo_box.remove(i) self.combo_box.remove(i)
break break
@@ -215,7 +220,9 @@ class LSPManagerUI(Gtk.Dialog):
if not lang_id or lang_id not in self.client_configs: return {} if not lang_id or lang_id not in self.client_configs: return {}
try: try:
lang_config = json.loads(self.client_configs[lang_id]) buffer = self.source_view.get_buffer()
json_str = buffer.get_text(*buffer.get_bounds(), -1)
lang_config = json.loads(json_str)
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
logger.error(f"Invalid JSON for {lang_id}: {e}") logger.error(f"Invalid JSON for {lang_id}: {e}")
return {} return {}

View File

@@ -0,0 +1,100 @@
{
"info": "https://github.com/python-lsp/python-lsp-server",
"command": "lsp-ws-proxy -- pylsp",
"alt-command": "pylsp",
"alt-command2": "lsp-ws-proxy --listen 4114 -- pylsp",
"alt-command3": "pylsp --ws --port 4114",
"socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=pylsp",
"initialization-options": {
"pylsp": {
"rope": {
"ropeFolder": "{user.home}/.config/newton/lsps/ropeproject"
},
"plugins": {
"ruff": {
"enabled": true,
"extendSelect": ["I"],
"lineLength": 80
},
"pycodestyle": {
"enabled": false
},
"pyflakes": {
"enabled": false
},
"pylint": {
"enabled": true
},
"mccabe": {
"enabled": false
},
"pylsp_rope": {
"rename": false
},
"rope_rename": {
"enabled": false
},
"rope_autoimport": {
"enabled": true
},
"rope_completion": {
"enabled": false,
"eager": false
},
"jedi_rename": {
"enabled": true
},
"jedi_completion": {
"enabled": true,
"include_class_objects": true,
"include_function_objects": true,
"fuzzy": false
},
"jedi": {
"root_dir": "file://{workspace.folder}",
"extra_paths": [
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
]
}
}
}
}
},
"python - jedi-language-server": {
"hidden": true,
"info": "https://pypi.org/project/jedi-language-server/",
"command": "jedi-language-server",
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
"socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
"initialization-options": {
"jediSettings": {
"autoImportModules": [],
"caseInsensitiveCompletion": true,
"debug": false
},
"completion": {
"disableSnippets": false,
"resolveEagerly": false,
"ignorePatterns": []
},
"markupKindPreferred": "markdown",
"workspace": {
"extraPaths": [
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
],
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
"symbols": {
"ignoreFolders": [
".nox",
".tox",
".venv",
"__pycache__",
"venv"
],
"maxSymbols": 20
}
}
}
}

View File

@@ -60,41 +60,4 @@
} }
} }
} }
},
"python - jedi-language-server": {
"hidden": true,
"info": "https://pypi.org/project/jedi-language-server/",
"command": "jedi-language-server",
"alt-command": "lsp-ws-proxy --listen 3030 -- jedi-language-server",
"socket": "ws://127.0.0.1:9999/python",
"socket-two": "ws://127.0.0.1:9999/?name=jedi-language-server",
"initialization-options": {
"jediSettings": {
"autoImportModules": [],
"caseInsensitiveCompletion": true,
"debug": false
},
"completion": {
"disableSnippets": false,
"resolveEagerly": false,
"ignorePatterns": []
},
"markupKindPreferred": "markdown",
"workspace": {
"extraPaths": [
"{user.home}/Portable_Apps/py-venvs/pylsp-venv/venv/lib/python3.10/site-packages"
],
"environmentPath": "{user.home}/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/python",
"symbols": {
"ignoreFolders": [
".nox",
".tox",
".venv",
"__pycache__",
"venv"
],
"maxSymbols": 20
}
}
}
} }

View File

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

View File

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

View File

@@ -0,0 +1,42 @@
# Python imports
# Lib imports
# Application imports
FOLD_NODES = {
"python": {
"function_definition",
"class_definition",
"if_statement",
"for_statement",
"while_statement",
"with_statement",
"try_statement",
},
"python3": {
"function_definition",
"class_definition",
"if_statement",
"for_statement",
"while_statement",
"with_statement",
"try_statement",
},
"java": {
"class_declaration",
"method_declaration",
"constructor_declaration",
"if_statement",
"for_statement",
"while_statement",
"switch_expression",
"block",
},
"json": {
"object",
"array",
},
}

View File

@@ -0,0 +1,24 @@
# Python imports
# Lib imports
# Application imports
def collapse_range(view, fold):
buffer = view.get_buffer()
start = buffer.get_iter_at_line(fold["start_line"] + 1)
end = buffer.get_iter_at_line(fold["end_line"] + 1)
buffer.apply_tag_by_name("invisible", start, end)
def expand_range(view, fold):
buffer = view.get_buffer()
start = buffer.get_iter_at_line(fold["start_line"] + 1)
end = buffer.get_iter_at_line(fold["end_line"] + 1)
buffer.remove_tag_by_name("invisible", start, end)

View File

@@ -0,0 +1,32 @@
# Python imports
# Lib imports
# Application imports
from .fold_types import FOLD_NODES
def visit_node(fold_types, node, ranges):
if node.type in fold_types:
start_line = node.start_point[0]
end_line = node.end_point[0]
if end_line > start_line:
ranges.append({
"start_line": start_line,
"end_line": end_line,
"id": (start_line, end_line, node.type),
})
for child in node.children:
visit_node(fold_types, child, ranges)
def get_folding_ranges(lang_name: str, ast):
root = ast.root_node
fold_types = FOLD_NODES.get(lang_name, set())
ranges = []
visit_node(fold_types, root, ranges)
return ranges

View File

@@ -0,0 +1,90 @@
# 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
# Application imports
from .folding_actions import collapse_range, expand_range
def handle_collapse(view, fold):
collapse_range(view, fold)
def handle_expand(view, fold):
expand_range(view, fold)
def handle_block_toggle(collapsed, view, fold):
if not collapsed:
handle_collapse(view, fold)
else:
handle_expand(view, fold)
def is_fold_hidden(buffer, fold):
iter_ = buffer.get_iter_at_line(fold["start_line"] + 1)
tags = iter_.get_tags()
return any(tag.get_property("invisible") for tag in tags)
def on_query_data(renderer, start_iter, end_iter, state, view):
line = start_iter.get_line()
if not line in view.fold_start_set:
renderer.set_text("", -1)
return
fold = next(
(f for f in view.fold_starts if f["start_line"] == line), None
)
collapsed = fold and is_fold_hidden(view.get_buffer(), fold)
renderer.set_text("" if collapsed else "", -1)
def on_click(view, event, renderer):
if not event.button == 1: return False
window = view.get_window(Gtk.TextWindowType.LEFT)
if not event.window == window: return False
x, y = view.window_to_buffer_coords(
Gtk.TextWindowType.LEFT, int(event.x), int(event.y)
)
_, iter_ = view.get_iter_at_location(x, y)
line = iter_.get_line()
if line not in view.fold_start_set: return False
for fold in view.fold_starts:
if not fold["start_line"] == line: continue
collapsed = is_fold_hidden(view.get_buffer(), fold)
handle_block_toggle(collapsed, view, fold)
renderer.queue_draw()
return True
return False
def setup_gutter(view):
gutter = view.get_gutter(Gtk.TextWindowType.LEFT)
buffer = view.get_buffer()
view.fold_starts = []
view.fold_start_set = set()
renderer = GtkSource.GutterRendererText()
renderer.set_size(12)
renderer.set_padding(2, -1)
renderer.query_data_id = renderer.connect("query-data", on_query_data, view)
view.collapse_click_id = view.connect("button-press-event", on_click, renderer)
gutter.insert(renderer, 0)
view.fold_renderer = renderer

View File

@@ -0,0 +1,7 @@
{
"name": "Code Fold",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {}
}

View File

@@ -0,0 +1,90 @@
# Python imports
# Lib imports
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib
from gi.repository import Gtk
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
from .fold_types import FOLD_NODES
from .folding_actions import collapse_range
from .folding_engine import get_folding_ranges
from .gutter_renderer import setup_gutter
class Plugin(PluginCode):
def _controller_message(self, event):
if isinstance(event, Code_Event_Types.FocusedViewEvent):
self.view = event.view
event = Event_Factory.create_event(
"get_file", buffer=self.view.get_buffer()
)
self.emit_to("files", event)
file = event.response
if not file: return
if file.ftype not in FOLD_NODES: return
if not hasattr(file, "ast"): return
buffer = file.buffer
if not buffer.get_tag_table().lookup("invisible"):
tag = buffer.create_tag("invisible")
tag.set_property("invisible", True)
self.update_gutter(file, self.view)
elif isinstance(event, Code_Event_Types.TextChangedEvent):
if event.file.ftype not in FOLD_NODES: return
if not hasattr(event.file, "ast"): return
self.schedule_update(event.file, self.view)
def load(self):
event = Event_Factory.create_event("get_source_views")
self.emit_to("source_views", event)
for view in event.response:
setup_gutter(view)
def unload(self):
event = Event_Factory.create_event("get_source_views")
self.emit_to("source_views", event)
for view in event.response:
view.fold_renderer.disconnect(view.fold_renderer.query_data_id)
view.disconnect(view.collapse_click_id)
gutter = view.get_gutter(Gtk.TextWindowType.LEFT)
gutter.remove(view.fold_renderer)
def run(self):
...
def schedule_update(self, file, view, delay=250):
if hasattr(view, "fold_update_source") and view.fold_update_source:
GLib.source_remove(view.fold_update_source)
def callback():
self.update_gutter(file, view)
if hasattr(view, "fold_renderer"):
view.fold_renderer.queue_draw()
view.fold_update_source = None
return False
view.fold_update_source = GLib.timeout_add(delay, callback)
def update_gutter(self, file, view):
view.fold_starts = get_folding_ranges(file.ftype, file.ast)
view.fold_start_set = {
fold["start_line"] for fold in view.fold_starts
}

View File

@@ -50,7 +50,14 @@ class Plugin(PluginCode):
def unload(self): def unload(self):
self.unregister_controller("tabs") self.unregister_controller("tabs")
self.tabs_controller.unload_tabs() self.tabs_controller.unload_tabs()
self.tabs_controller.tabs_widget.destroy()
tabs_widget = self.tabs_controller.tabs_widget
viewport = tabs_widget.get_parent()
scrolled_win = viewport.get_parent()
tabs_widget.destroy()
viewport.destroy()
scrolled_win.destroy()
self.tabs_controller.tabs_widget = None self.tabs_controller.tabs_widget = None
self.tabs_controller = None self.tabs_controller = None

View File

@@ -52,8 +52,8 @@ class Plugin(PluginCode):
) )
self.emit_to("source_views", event) self.emit_to("source_views", event)
source_view = event.response scrolled_win, source_view = event.response
telescope.set_source_view(source_view) telescope.set_source_view(scrolled_win, source_view)
event = Event_Factory.create_event( event = Event_Factory.create_event(
"register_completer", "register_completer",

View File

@@ -99,13 +99,9 @@ class Telescope(Gtk.Dialog):
def unmap_parent_resize_event(self, parent): def unmap_parent_resize_event(self, parent):
parent.disconnect(self.size_allocate_id) parent.disconnect(self.size_allocate_id)
def set_source_view(self, source_view): def set_source_view(self, scrolled_win, source_view):
scrolled_win = Gtk.ScrolledWindow()
self.source_view = source_view self.source_view = source_view
scrolled_win.add(self.source_view)
self.main_box.pack_end(scrolled_win, True, True, 0) self.main_box.pack_end(scrolled_win, True, True, 0)
scrolled_win.show_all() scrolled_win.show_all()
def _handle_destroy(self, widget): def _handle_destroy(self, widget):

View File

@@ -6,9 +6,10 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from core.widgets.save_file_dialog import SaveFileDialog
from core.widgets.controls.open_files_button import OpenFilesButton
from .code.code_container import CodeContainer from .code.code_container import CodeContainer
from ..widgets.save_file_dialog import SaveFileDialog
from ..widgets.controls.open_files_button import OpenFilesButton

View File

@@ -6,10 +6,6 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ...widgets.code.code_base import CodeBase
from ...widgets.separator_widget import Separator
from .editors_container import EditorsContainer from .editors_container import EditorsContainer
@@ -27,6 +23,9 @@ class CodeContainer(Gtk.Box):
def _setup_styling(self): def _setup_styling(self):
self.ctx = self.get_style_context()
self.ctx.add_class("code-container")
self.set_orientation(Gtk.Orientation.VERTICAL) self.set_orientation(Gtk.Orientation.VERTICAL)
def _setup_signals(self): def _setup_signals(self):
@@ -37,18 +36,4 @@ class CodeContainer(Gtk.Box):
def _load_widgets(self): def _load_widgets(self):
widget_registery.expose_object("code-container", self) widget_registery.expose_object("code-container", self)
self.add( EditorsContainer() )
code_base = CodeBase()
self.add( self._create_editor_widget(code_base) )
def _create_editor_widget(self, code_base: CodeBase):
editors_container = Gtk.Box()
widget_registery.expose_object("editors-container", editors_container)
editors_container.add( Separator("separator_left") )
editors_container.add( EditorsContainer(code_base) )
editors_container.add( Separator("separator_right") )
return editors_container

View File

@@ -4,18 +4,17 @@
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
#from gi.repository import GLib
# Application imports # Application imports
from core.widgets.code.code_base import CodeBase
from core.widgets.separator_widget import Separator
class EditorsContainer(Gtk.Paned): class EditorsContainer(Gtk.Box):
def __init__(self, code_base: any): def __init__(self):
super(EditorsContainer, self).__init__() super(EditorsContainer, self).__init__()
self.code_base = code_base
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
self._subscribe_to_events() self._subscribe_to_events()
@@ -24,11 +23,11 @@ class EditorsContainer(Gtk.Paned):
def _setup_styling(self): def _setup_styling(self):
self.ctx = self.get_style_context() self.ctx = self.get_style_context()
self.ctx.add_class("paned-editors-container") self.ctx.add_class("editors-container")
self.set_hexpand(True) self.set_hexpand(True)
self.set_vexpand(True) self.set_vexpand(True)
self.set_wide_handle(True) self.set_size_request(320, -1)
def _setup_signals(self): def _setup_signals(self):
self.connect("map", self._init_map) self.connect("map", self._init_map)
@@ -37,29 +36,18 @@ class EditorsContainer(Gtk.Paned):
... ...
def _load_widgets(self): def _load_widgets(self):
self.scrolled_win1, \ box = Gtk.Box()
self.scrolled_win2 = self._create_views() widget_registery.expose_object("editors-container", self)
self.code_base = CodeBase()
scrolled_win, \
source_view = self.code_base.create_source_view()
self.pack1( self.scrolled_win1, True, True ) box.add( scrolled_win )
self.pack2( self.scrolled_win2, True, True ) self.add( Separator("separator_left") )
self.add( box )
def _create_views(self): self.add( Separator("separator_right") )
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 _init_map(self, view):
self.disconnect_by_func( self._init_map ) self.disconnect_by_func( self._init_map )
self.code_base.first_map_load() self.code_base.first_map_load()
self.code_base = None
del self.code_base del self.code_base

View File

@@ -6,8 +6,8 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.separator_widget import Separator from core.widgets.separator_widget import Separator
from ..widgets.vte_widget import VteWidget from core.widgets.vte_widget import VteWidget

View File

@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.separator_widget import Separator from core.widgets.separator_widget import Separator

View File

@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.separator_widget import Separator from core.widgets.separator_widget import Separator

View File

@@ -6,7 +6,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.separator_widget import Separator from core.widgets.separator_widget import Separator

View File

@@ -18,16 +18,12 @@ class BaseControllerMixin:
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.isfile(arg):
files.append( f"DIR|{arg.replace('file://', '')}" ) files.append(f"{arg}")
continue
# NOTE: If passing line number with file split against : if os.path.isdir(arg):
if os.path.isfile( arg.replace("file://", "").split(":")[0] ): message = f"DIR|{arg}"
files.append( f"FILE|{arg.replace('file://', '')}" ) ipc_server.send_ipc_message(message)
continue
logger.info(f"Not a File: {arg}")
if not files: return if not files: return

View File

@@ -34,7 +34,6 @@ class CodeBase:
completion_controller = CompletionController() completion_controller = CompletionController()
source_views_controller = SourceViewsController() source_views_controller = SourceViewsController()
# self.controller_manager.register_controller("base", self)
self.controller_manager.register_controller("files", files_controller) self.controller_manager.register_controller("files", files_controller)
self.controller_manager.register_controller("commands", commands_controller) self.controller_manager.register_controller("commands", commands_controller)
self.controller_manager.register_controller("completion", completion_controller) self.controller_manager.register_controller("completion", completion_controller)
@@ -43,12 +42,13 @@ class CodeBase:
self.controller_manager.register_controller("widgets", widget_registery) self.controller_manager.register_controller("widgets", widget_registery)
def create_source_view(self): def create_source_view(self):
scrolled_win, \
source_view = self.controller_manager["source_views"].create_source_view() source_view = self.controller_manager["source_views"].create_source_view()
self.controller_manager["completion"].register_completer( self.controller_manager["completion"].register_completer(
source_view.get_completion() source_view.get_completion()
) )
return source_view return scrolled_win, source_view
def first_map_load(self): def first_map_load(self):
self.controller_manager["source_views"].first_map_load() self.controller_manager["source_views"].first_map_load()

View File

@@ -2,4 +2,4 @@
Code Command System Package Code Command System Package
""" """
from .command_system import CommandSystem from .source_view_command_system import SourceViewCommandSystem

View File

@@ -1,120 +0,0 @@
# Python imports
# Lib imports
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from ..mixins.command_system_mixin import CommandSystemMixin
from ..source_view import SourceView
from . import commands
class CommandSystem(CommandSystemMixin):
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
return method.execute(*args, **kwargs)
def exec_with_args(self, command: str, *args, **kwargs) -> any:
if not hasattr(commands, command): return
method = getattr(commands, command)
return method.execute(*args, **kwargs)
def add_command(self, command_name: str, command: callable):
setattr(commands, command_name, command)
def remove_command(self, command_name: str, command: callable):
if hasattr(commands, command_name):
delattr(commands, command_name)
def emit(self, event: Code_Event_Types.CodeEvent):
""" Monkey patch 'emit' from command controller... """
...
def emit_to(self, controller: str, event: Code_Event_Types.CodeEvent):
""" Monkey patch 'emit_to' from command controller... """
...
# def filter_out_loaded_files(self, uris: list[str]):
# event = Event_Factory.create_event(
# "filter_out_loaded_files",
# uris = uris
# )
#
# self.emit_to("files", event)
#
# return event.response
#
# def set_info_labels(self, data: tuple[str]):
# event = Event_Factory.create_event(
# "set_info_labels",
# info = data
# )
#
# self.emit_to("plugins", event)
#
# def get_file(self, view: SourceView):
# event = Event_Factory.create_event(
# "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_event(
# "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_event(
# "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)

View File

@@ -20,9 +20,14 @@ def execute(
ctx = view.get_parent().get_style_context() ctx = view.get_parent().get_style_context()
ctx.add_class("source-view-focused") ctx.add_class("source-view-focused")
if view.sibling_right: lview = view.sibling_left
ctx = view.sibling_right.get_parent().get_style_context() while lview is not None:
elif view.sibling_left: ctx = lview.get_parent().get_style_context()
ctx = view.sibling_left.get_parent().get_style_context()
ctx.remove_class("source-view-focused") ctx.remove_class("source-view-focused")
lview = lview.sibling_left
rview = view.sibling_right
while rview is not None:
ctx = rview.get_parent().get_style_context()
ctx.remove_class("source-view-focused")
rview = rview.sibling_right

View File

@@ -4,12 +4,19 @@
# Application imports # Application imports
from libs.event_factory import Event_Factory, Code_Event_Types from libs.event_factory import Event_Factory, Code_Event_Types
from libs.command_system import CommandSystem
from ..source_view import SourceView from ..source_view import SourceView
from . import commands
class SourceViewCommandSystem(CommandSystem):
def __init__(self):
super(SourceViewCommandSystem, self).__init__(commands)
class CommandSystemMixin:
def toggle_plugins_ui(self): def toggle_plugins_ui(self):
event = Event_Factory.create_event( "toggle_plugins_ui" ) event = Event_Factory.create_event( "toggle_plugins_ui" )
@@ -81,3 +88,11 @@ class CommandSystemMixin:
) )
self.emit_to("completion", event) self.emit_to("completion", event)
def emit(self, event: Code_Event_Types.CodeEvent):
""" Monkey patch 'emit' from command controller... """
...
def emit_to(self, controller: str, event: Code_Event_Types.CodeEvent):
""" Monkey patch 'emit_to' from command controller... """
...

View File

@@ -7,7 +7,7 @@ from libs.controllers.controller_base import ControllerBase
from libs.event_factory import Code_Event_Types from libs.event_factory import Code_Event_Types
from ..command_system import CommandSystem from ..command_system import SourceViewCommandSystem
@@ -17,14 +17,19 @@ class CommandsController(ControllerBase, list):
def _controller_message(self, event: Code_Event_Types.CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.GetNewCommandSystemEvent): if isinstance(event, Code_Event_Types.CreateCommandSystemEvent):
event.response = self.get_new_command_system() event.response = self.create_command_system()
elif isinstance(event, Code_Event_Types.RemovedSourceViewEvent):
self.remove_command_system(event)
def get_new_command_system(self): def create_command_system(self):
command_system = CommandSystem() command_system = SourceViewCommandSystem()
command_system.emit = self.emit command_system.emit = self.emit
command_system.emit_to = self.emit_to command_system.emit_to = self.emit_to
self.append(command_system) self.append(command_system)
return command_system return command_system
def remove_command_system(self, event: Code_Event_Types.RemovedSourceViewEvent):
self.remove(event.view.command)

View File

@@ -46,14 +46,24 @@ class MarkerManager(MarkSupportMixin):
start_itr = buffer.get_iter_at_mark(start_mark) start_itr = buffer.get_iter_at_mark(start_mark)
end_itr = buffer.get_iter_at_mark(end_mark) end_itr = buffer.get_iter_at_mark(end_mark)
self._proc_move(
buffer, is_forward, is_selection, mode, mark_hash,
start_mark, end_mark, has_selection, start_itr, end_itr
)
def _proc_move(
self, buffer, is_forward: bool, is_selection: bool, mode: str,
mark_hash, start_mark, end_mark, has_selection, start_itr, end_itr
):
if is_selection: if is_selection:
if mark_hash:
self.buffer_markers[mark_hash]["is_selection"] = True self.buffer_markers[mark_hash]["is_selection"] = True
self._move_iter(buffer, end_itr, mode, is_forward) self._move_iter(buffer, end_itr, mode, is_forward)
buffer.move_mark(end_mark, end_itr) buffer.move_mark(end_mark, end_itr)
self._apply_selection(buffer, start_itr, end_itr) self._apply_selection(buffer, start_itr, end_itr)
continue return
if has_selection: if has_selection:
caret_itr = buffer.get_iter_at_mark(end_mark) caret_itr = buffer.get_iter_at_mark(end_mark)
@@ -67,15 +77,14 @@ class MarkerManager(MarkSupportMixin):
self.collapse_selection(buffer, mark_hash, start_mark, end_mark, is_forward) self.collapse_selection(buffer, mark_hash, start_mark, end_mark, is_forward)
if mode == "word": if mode == "word":
if not can_move: continue if not can_move: return
itr = caret_itr itr = caret_itr
self._move_iter(buffer, itr, mode, is_forward) self._move_iter(buffer, itr, mode, is_forward)
buffer.move_mark(start_mark, itr) buffer.move_mark(start_mark, itr)
buffer.move_mark(end_mark, itr) buffer.move_mark(end_mark, itr)
continue return
# No selection - move both anchor and caret together # No selection - move both anchor and caret together
self._move_iter(buffer, end_itr, mode, is_forward) self._move_iter(buffer, end_itr, mode, is_forward)
@@ -86,6 +95,7 @@ class MarkerManager(MarkSupportMixin):
def collapse_selection(self, def collapse_selection(self,
buffer, mark_hash, start_mark, end_mark, is_forward: bool buffer, mark_hash, start_mark, end_mark, is_forward: bool
): ):
if mark_hash:
self.buffer_markers[mark_hash]["is_selection"] = False self.buffer_markers[mark_hash]["is_selection"] = False
start_itr = buffer.get_iter_at_mark(start_mark) start_itr = buffer.get_iter_at_mark(start_mark)
@@ -105,19 +115,28 @@ class MarkerManager(MarkSupportMixin):
buffer.move_mark(start_mark, collapse_itr) buffer.move_mark(start_mark, collapse_itr)
buffer.move_mark(end_mark, collapse_itr) buffer.move_mark(end_mark, collapse_itr)
def move_word_snake_case(self, itr: Gtk.TextIter, count: int): def move_along_word(self, itr: Gtk.TextIter, count: int):
def is_word(ch): def not_is_word(ch: str):
return not is_word(ch)
def is_word(ch: str):
return ch and (ch.isalnum() or ch == "_") return ch and (ch.isalnum() or ch == "_")
def step(fwd): def is_special(ch: str):
return ch in "-"
def is_punct(ch: str):
return ch in ".;?!"
def step(fwd: bool):
return itr.forward_cursor_position() if fwd else itr.backward_cursor_position() return itr.forward_cursor_position() if fwd else itr.backward_cursor_position()
def peek(fwd): def peek(fwd: bool):
if fwd: return itr.get_char() if fwd: return itr.get_char()
tmp = itr.copy() tmp = itr.copy()
return tmp.backward_cursor_position() and tmp.get_char() return tmp.backward_cursor_position() and tmp.get_char()
def walk(fwd, cond): def walk(fwd, cond: callable):
while True: while True:
ch = peek(fwd) ch = peek(fwd)
if not cond(ch): break if not cond(ch): break
@@ -126,23 +145,22 @@ class MarkerManager(MarkSupportMixin):
return True return True
fwd = count > 0 fwd = count > 0
for _ in range( abs(count) ):
ch = peek(fwd)
for _ in range(abs(count)): if is_special(ch) or is_punct(ch):
ch = itr.get_char() if fwd else peek(False) step(fwd)
elif is_word(ch):
if is_word(ch):
# inside word
if not walk(fwd, is_word): return if not walk(fwd, is_word): return
else: else:
# in separators -> skip them, then the word if not walk(fwd, not_is_word): return
if not walk(fwd, lambda c: not is_word(c)): return
if not walk(fwd, is_word): return if not walk(fwd, is_word): return
def _move_iter(self, buffer, itr_, mode: str, is_forward: bool): def _move_iter(self, buffer, itr_, mode: str, is_forward: bool):
if mode == "char": if mode == "char":
itr_.forward_char() if is_forward else itr_.backward_char() itr_.forward_char() if is_forward else itr_.backward_char()
elif mode == "word": elif mode == "word":
self.move_word_snake_case(itr_, 1 if is_forward else -1) self.move_along_word(itr_, 1 if is_forward else -1)
elif mode == "line": elif mode == "line":
line = itr_.get_line() line = itr_.get_line()
offset = itr_.get_line_offset() offset = itr_.get_line_offset()

View File

@@ -1,6 +1,9 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports # Application imports
from libs.controllers.controller_base import ControllerBase from libs.controllers.controller_base import ControllerBase
@@ -29,6 +32,8 @@ class SourceViewsController(ControllerBase, list):
def _controller_message(self, event: Code_Event_Types.CodeEvent): def _controller_message(self, event: Code_Event_Types.CodeEvent):
if isinstance(event, Code_Event_Types.CreateSourceViewEvent): if isinstance(event, Code_Event_Types.CreateSourceViewEvent):
event.response = self.create_source_view(event.state) event.response = self.create_source_view(event.state)
elif isinstance(event, Code_Event_Types.RemoveSourceViewEvent):
self._remove_source_view(event)
elif isinstance(event, Code_Event_Types.RemovedFileEvent): elif isinstance(event, Code_Event_Types.RemovedFileEvent):
self._remove_file(event) self._remove_file(event)
elif isinstance(event, Code_Event_Types.RegisterCommandEvent): elif isinstance(event, Code_Event_Types.RegisterCommandEvent):
@@ -86,7 +91,7 @@ class SourceViewsController(ControllerBase, list):
) )
def _get_command_system(self): def _get_command_system(self):
event = Event_Factory.create_event("get_new_command_system") event = Event_Factory.create_event("create_command_system")
self.message_to("commands", event) self.message_to("commands", event)
command = event.response command = event.response
@@ -103,15 +108,33 @@ class SourceViewsController(ControllerBase, list):
source_view.set_buffer(event.next_file.buffer) source_view.set_buffer(event.next_file.buffer)
def _remove_source_view(self, event: Code_Event_Types.RemovedFileEvent):
event = Event_Factory.create_event("removed_source_view", view = event.view)
self.message(event)
self.remove(event.view)
self.signal_mapper.disconnect_signals(event.view)
def create_source_view(self, state: SourceViewStates = SourceViewStates.INSERT): def create_source_view(self, state: SourceViewStates = SourceViewStates.INSERT):
scrolled_win: Gtk.ScrolledWindow = Gtk.ScrolledWindow()
source_view: SourceView = SourceView(state) source_view: SourceView = SourceView(state)
source_view.command = self._get_command_system() source_view.command = self._get_command_system()
source_view.command.set_data(source_view)
scrolled_win.set_hexpand(True)
scrolled_win.set_vexpand(True)
source_view.command.set_data(source_view)
self.signal_mapper.connect_signals(source_view) self.signal_mapper.connect_signals(source_view)
self.append(source_view) self.append(source_view)
return source_view scrolled_win.add(source_view)
event = Event_Factory.create_event(
"created_source_view", view = source_view
)
self.emit(event)
return scrolled_win, source_view
def first_map_load(self): def first_map_load(self):
for source_view in self: for source_view in self:

View File

@@ -59,30 +59,56 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
buffer.insert(start_itr, text, -1) buffer.insert(start_itr, text, -1)
self.marker_manager.apply_to_marks(buffer, replace_word) self.marker_manager.apply_to_marks(buffer, replace_word)
return True return True
def move_cursor(self, source_view, step, count, is_selection, emit): def move_cursor(
self, source_view, step, count, is_selection,
emit = None, ignore_leader: bool = False
):
is_forward = count > 0 is_forward = count > 0
buffer = source_view.get_buffer() buffer = source_view.get_buffer()
if step in [ start_mark = buffer.get_insert()
Gtk.MovementStep.LOGICAL_POSITIONS, end_mark = buffer.get_selection_bound()
Gtk.MovementStep.VISUAL_POSITIONS start_itr = buffer.get_iter_at_mark(start_mark)
]: end_itr = buffer.get_iter_at_mark(end_mark)
self.marker_manager.move_by_char(buffer, is_forward, is_selection) has_selection = not start_itr.equal(end_itr)
elif step == Gtk.MovementStep.WORDS:
self.marker_manager.move_by_word(buffer, is_forward, is_selection)
elif step == Gtk.MovementStep.DISPLAY_LINES:
self.marker_manager.move_by_line(buffer, is_forward, is_selection)
self._signal_cursor_moved(source_view, emit) step_map = {
Gtk.MovementStep.LOGICAL_POSITIONS: ("char", self.marker_manager.move_by_char),
Gtk.MovementStep.VISUAL_POSITIONS: ("char", self.marker_manager.move_by_char),
Gtk.MovementStep.WORDS: ("word", self.marker_manager.move_by_word),
Gtk.MovementStep.DISPLAY_LINES: ("line", self.marker_manager.move_by_line),
}
return False kind, move_fn = step_map[step]
move_fn(buffer, is_forward, is_selection)
if ignore_leader: return True
self.marker_manager._proc_move(
buffer,
is_forward,
is_selection,
kind,
None,
start_mark,
end_mark,
has_selection,
start_itr,
end_itr,
)
# self._signal_cursor_moved(source_view, emit)
return True
def key_press_event(self, source_view, event, key_mapper): def key_press_event(self, source_view, event, key_mapper):
char = key_mapper.get_raw_keyname(event).upper() char = key_mapper.get_raw_keyname(event).upper()
self.is_control = key_mapper.is_control(event) self.is_control = key_mapper.is_control(event)
self.is_shift = key_mapper.is_shift(event) self.is_shift = key_mapper.is_shift(event)
self.is_super = key_mapper.is_super(event)
if char.upper() in ["BACKSPACE", "DELETE", "ENTER"]: if char.upper() in ["BACKSPACE", "DELETE", "ENTER"]:
self.marker_manager.process_cursor_action( self.marker_manager.process_cursor_action(
@@ -91,6 +117,9 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
) )
return False return False
if self._do_cursor_moved(source_view, char):
return True
return super().key_press_event(source_view, event, key_mapper) return super().key_press_event(source_view, event, key_mapper)
def button_press_event(self, source_view, event): def button_press_event(self, source_view, event):
@@ -99,6 +128,56 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
def button_release_event(self, source_view, event): def button_release_event(self, source_view, event):
self.marker_manager.button_release_event(source_view, event) self.marker_manager.button_release_event(source_view, event)
def _do_cursor_moved(self, source_view, char: str):
key = char.upper()
if key not in {"LEFT", "RIGHT", "UP", "DOWN"}: return False
direction = {
"LEFT": -1,
"RIGHT": 1,
"UP": -1,
"DOWN": 1,
}[key]
is_horizontal = key in {"LEFT", "RIGHT"}
step = \
Gtk.MovementStep.VISUAL_POSITIONS if is_horizontal else Gtk.MovementStep.DISPLAY_LINES
count = direction
is_selection = self.is_shift
if is_horizontal:
if self.is_control:
step = Gtk.MovementStep.WORDS
if self.is_control and self.is_shift:
is_selection = True
if self.is_super:
return self.move_cursor(
source_view,
step,
count,
is_selection = False,
emit = None,
ignore_leader = True,
)
else:
if self.is_super:
return self.move_cursor(
source_view,
step,
count,
is_selection,
emit = None,
ignore_leader = True,
)
return self.move_cursor(
source_view,
step = step,
count = count,
is_selection = is_selection,
)
def _signal_cursor_moved(self, source_view, emit): def _signal_cursor_moved(self, source_view, emit):
buffer = source_view.get_buffer() buffer = source_view.get_buffer()
itr = buffer.get_iter_at_mark( buffer.get_insert() ) itr = buffer.get_iter_at_mark( buffer.get_insert() )

View File

@@ -161,6 +161,10 @@ class KeyMapper:
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK) modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
return modifiers & Gdk.ModifierType.SHIFT_MASK return modifiers & Gdk.ModifierType.SHIFT_MASK
def is_super(self, eve):
modifiers = Gdk.ModifierType(eve.get_state() & ~Gdk.ModifierType.LOCK_MASK)
return modifiers & Gdk.ModifierType.SUPER_MASK
def get_raw_keyname(self, eve) -> str: def get_raw_keyname(self, eve) -> str:
return Gdk.keyval_name(eve.keyval) return Gdk.keyval_name(eve.keyval)

View File

@@ -155,9 +155,10 @@ class SourceFile(GtkSource.File):
self.buffer.unblock_modified_changed_signal() self.buffer.unblock_modified_changed_signal()
def is_externally_modified(self) -> bool: def is_externally_modified(self) -> bool:
if self.fname == "buffer": return
stat = os.stat(self.fpath) stat = os.stat(self.fpath)
current = (stat.st_mtime_ns, stat.st_size) current = (stat.st_mtime_ns, stat.st_size)
is_modified = \ is_modified = \
hasattr(self, "last_state") and not current == self.last_state hasattr(self, "last_state") and not current == self.last_state

View File

@@ -0,0 +1,50 @@
# Python imports
import types
# Lib imports
# Application imports
from .event_factory import Event_Factory, Code_Event_Types
class CommandSystem:
def __init__(self, commands: dict | types.ModuleType):
super(CommandSystem, self).__init__()
self.commands: dict | types.ModuleType = commands
self.data: tuple = ()
def set_data(self, *args, **kwargs):
self.data = (args, kwargs)
def exec(self, command: str) -> any:
"""
The 'exec' method passes the default 'self.data' to commands where custom args are not needed.
Ex: The 'code' widget has many internally created commands that
only need 'source_view' and so 'set_data' is called to set that.
"""
if not hasattr(self.commands, command): return
method = getattr(self.commands, command)
args, kwargs = self.data
return method.execute(*args, **kwargs)
def exec_with_args(self, command: str, *args, **kwargs) -> any:
"""
The 'exec_with_args' method passes custom args with the understanding
that the recipient has proper method signature to accept it- whether
*args or **kwargs or something else entirely.
"""
if not hasattr(self.commands, command): return
method = getattr(self.commands, command)
return method.execute(*args, **kwargs)
def add_command(self, command_name: str, command: callable):
setattr(self.commands, command_name, command)
def remove_command(self, command_name: str, command: callable):
if hasattr(self.commands, command_name):
delattr(self.commands, command_name)

View File

@@ -6,6 +6,9 @@
from .code_event import CodeEvent from .code_event import CodeEvent
from .toggle_plugins_ui_event import TogglePluginsUiEvent from .toggle_plugins_ui_event import TogglePluginsUiEvent
from .create_source_view_event import CreateSourceViewEvent from .create_source_view_event import CreateSourceViewEvent
from .created_source_view_event import CreatedSourceViewEvent
from .remove_source_view_event import RemoveSourceViewEvent
from .removed_source_view_event import RemovedSourceViewEvent
from .register_completer_event import RegisterCompleterEvent from .register_completer_event import RegisterCompleterEvent
from .unregister_completer_event import UnregisterCompleterEvent from .unregister_completer_event import UnregisterCompleterEvent
from .register_provider_event import RegisterProviderEvent from .register_provider_event import RegisterProviderEvent
@@ -20,7 +23,7 @@ from .filter_out_loaded_files_event import FilterOutLoadedFilesEvent
from .get_active_view_event import GetActiveViewEvent from .get_active_view_event import GetActiveViewEvent
from .get_source_views_event import GetSourceViewsEvent from .get_source_views_event import GetSourceViewsEvent
from .get_new_command_system_event import GetNewCommandSystemEvent from .create_command_system_event import CreateCommandSystemEvent
from .request_completion_event import RequestCompletionEvent from .request_completion_event import RequestCompletionEvent
from .cursor_moved_event import CursorMovedEvent from .cursor_moved_event import CursorMovedEvent
from .modified_changed_event import ModifiedChangedEvent from .modified_changed_event import ModifiedChangedEvent

View File

@@ -9,5 +9,5 @@ from .code_event import CodeEvent
@dataclass @dataclass
class GetNewCommandSystemEvent(CodeEvent): class CreateCommandSystemEvent(CodeEvent):
... ...

View File

@@ -0,0 +1,14 @@
# Python imports
from dataclasses import dataclass
# Lib imports
# Application imports
from .code_event import CodeEvent
from libs.dto.states.source_view_states import SourceViewStates
@dataclass
class CreatedSourceViewEvent(CodeEvent):
...

View File

@@ -0,0 +1,14 @@
# Python imports
from dataclasses import dataclass
# Lib imports
# Application imports
from .code_event import CodeEvent
from libs.dto.states.source_view_states import SourceViewStates
@dataclass
class RemoveSourceViewEvent(CodeEvent):
...

View File

@@ -0,0 +1,14 @@
# Python imports
from dataclasses import dataclass
# Lib imports
# Application imports
from .code_event import CodeEvent
from libs.dto.states.source_view_states import SourceViewStates
@dataclass
class RemovedSourceViewEvent(CodeEvent):
...

View File

@@ -13,9 +13,11 @@ from .requests import Requests
class Manifest: class Manifest:
name: str = "" name: str = ""
author: str = "" author: str = ""
credit: str = "" description: str = ""
version: str = "0.0.1" version: str = "0.0.1"
support: str = "support@mail.com" support: str = "support@mail.com"
credit: str = ""
copyright: str = "GPLv2"
pre_launch: bool = False pre_launch: bool = False
autoload: bool = True autoload: bool = True
requests: Requests = field(default_factory = lambda: Requests()) requests: Requests = field(default_factory = lambda: Requests())

View File

@@ -56,7 +56,7 @@ class ManifestManager:
if not manifest.autoload: if not manifest.autoload:
self.manual_launch_manifests.append(manifest_meta) self.manual_launch_manifests.append(manifest_meta)
return return manifest_meta
if manifest.pre_launch: if manifest.pre_launch:
self.pre_launch_manifests.append(manifest_meta) self.pre_launch_manifests.append(manifest_meta)

View File

@@ -48,13 +48,13 @@ class PluginReloadMixin:
def remove_plugin(self, file: str) -> None: def remove_plugin(self, file: str) -> None:
logger.info(f"Removing plugin: {file.get_uri()}") logger.info(f"Removing plugin: {file.get_uri()}")
for manifest_meta in self._plugin_collection[:]:
if not manifest_meta.folder in file.get_uri(): continue
manifest_meta.instance.unload() manifests = self._manifest_manager.pre_launch_manifests \
manifest_meta.instance = None + self._manifest_manager.post_launch_manifests \
self._plugin_collection.remove(manifest_meta) + self._manifest_manager.manual_launch_manifests
self.plugins_ui.remove_row(manifest_meta)
for manifest_meta in manifests:
if not manifest_meta.folder in file.get_uri(): continue
if manifest_meta in self._manifest_manager.pre_launch_manifests: if manifest_meta in self._manifest_manager.pre_launch_manifests:
self._manifest_manager.pre_launch_manifests.remove(manifest_meta) self._manifest_manager.pre_launch_manifests.remove(manifest_meta)
@@ -63,4 +63,15 @@ class PluginReloadMixin:
elif manifest_meta in self._manifest_manager.manual_launch_manifests: elif manifest_meta in self._manifest_manager.manual_launch_manifests:
self._manifest_manager.manual_launch_manifests.remove(manifest_meta) self._manifest_manager.manual_launch_manifests.remove(manifest_meta)
self.plugins_ui.remove_row(manifest_meta)
break
del manifests
for manifest_meta in self._plugin_collection[:]:
if not manifest_meta.folder in file.get_uri(): continue
manifest_meta.instance.unload()
manifest_meta.instance = None
self._plugin_collection.remove(manifest_meta)
break break

View File

@@ -76,6 +76,7 @@ class PluginsUI(Gtk.Dialog):
toggle_bttn.toggle_id = \ toggle_bttn.toggle_id = \
toggle_bttn.connect("toggled", callback, manifest_meta) toggle_bttn.connect("toggled", callback, manifest_meta)
box.toggle_bttn = toggle_bttn
box.add(plugin_lbl) box.add(plugin_lbl)
box.add(author_lbl) box.add(author_lbl)
@@ -96,5 +97,5 @@ class PluginsUI(Gtk.Dialog):
toggle_bttn.disconnect(toggle_bttn.toggle_id) toggle_bttn.disconnect(toggle_bttn.toggle_id)
self.list_box.remove(row) self.list_box.remove(row)
box.destroy() child.destroy()
break break

View File

@@ -44,18 +44,6 @@
}, },
"toggle_plugins_ui": { "toggle_plugins_ui": {
"released": "<Control><Shift>p" "released": "<Control><Shift>p"
},
"focus_left_sibling": {
"released": "<Control>Page_Up"
},
"focus_right_sibling": {
"released": "<Control>Page_Down"
},
"move_to_left_sibling": {
"released": "<Control><Shift>Up"
},
"move_to_right_sibling": {
"released": "<Control><Shift>Down"
} }
} }
} }