Compare commits
11 Commits
77a3b71d31
...
release/ne
| Author | SHA1 | Date | |
|---|---|---|---|
| 70877a7ee1 | |||
| cb73f6b3b0 | |||
| e6eaa1d83c | |||
| 62731ae766 | |||
| b5cec0d049 | |||
| c821f30880 | |||
| 13908d7ba7 | |||
| ec69d4db73 | |||
| 511138316a | |||
| d6e0823e21 | |||
| 54e7b58c24 |
32
README.md
32
README.md
@@ -1,25 +1,13 @@
|
||||
# Python-With-Gtk-Template
|
||||
A template project for Python with Gtk applications.
|
||||
|
||||
### Requirements
|
||||
* PyGObject (Gtk introspection library)
|
||||
* pygobject-stubs (For actually getting pylsp or python-language-server to auto complete in LSPs. Do if GTK3 --no-cache-dir --config-settings=config=Gtk3,Gdk3,Soup2)
|
||||
* pyxdg (Desktop ".desktop" file parser)
|
||||
* setproctitle (Define process title to search and kill more easily)
|
||||
* sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy)
|
||||
# Newton
|
||||
A Python + Gtk 3 based quasi-IDE.
|
||||
|
||||
### Note
|
||||
* pyrightconfig.json can prompt IDEs that use pyright lsp on where imports are located- look at venvPath and venv. "venvPath" is parent path of "venv" where "venv" is just the name of the folder under the parent path that is the python created venv.
|
||||
* Move respetive sub folder content under user_config to the same places in Linux. Though, user/share/<app name> can go to ~/.config folder if prefered.
|
||||
* In additiion, place the plugins folder in the same app folder you moved to /usr/share/<app name> or ~/.config/<app name> .
|
||||
There are a "\<change_me\>" strings and files that need to be set according to your app's name located at:
|
||||
* \_\_builtins\_\_.py
|
||||
* user_config/bin/app_name
|
||||
* user_config/usr/share/app_name
|
||||
* user_config/usr/share/app_name/icons/app_name.png
|
||||
* user_config/usr/share/app_name/icons/app_name-64x64.png
|
||||
* user_config/usr/share/applications/app_name.desktop
|
||||
[TODO](TODO.md)
|
||||
|
||||
|
||||
For the user_config, after changing names and files, copy all content to their respective destinations.
|
||||
The logic follows Debian Dpkg packaging and its placement logic.
|
||||
### Images
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
19
TODO.md
19
TODO.md
@@ -1,22 +1,19 @@
|
||||
___
|
||||
### Add
|
||||
1. Add Godot LSP Client
|
||||
1. Add TreeSitter
|
||||
1. Add Collapsable code blocks
|
||||
1. Add Godot LSP Client
|
||||
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
|
||||
|
||||
___
|
||||
### Change
|
||||
1. Make **telescope** plugin a generic base to allow query mode additions through plugins
|
||||
1. Make **lsp_manager** hard coded values configurable, plus add fields to UI
|
||||
___
|
||||
### Fix
|
||||
- Fix **telescope** search selection when items hidden such that
|
||||
hidden items don't get selected on up/down key
|
||||
- Fix to make acive tab on **tabs_bar** scroll to center
|
||||
- Fix **file_state_watcher** to prompt refrsh if external changes applied
|
||||
- Fix on lsp client unload to close files lsp side and unload server endpoint
|
||||
- Fix multi-select <Shift\><Ctrl\> left/right block select movement de-sync
|
||||
from leader when '_' in word
|
||||
1. Make **lsp_manager** hard coded values configurable, plus add respective fields to UI
|
||||
|
||||
___
|
||||
### Fix
|
||||
- Fix on lsp client unload to close files lsp side and unload server endpoint
|
||||
|
||||
___
|
||||
|
||||
BIN
images/pic1.png
Normal file
BIN
images/pic1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
BIN
images/pic2.png
Normal file
BIN
images/pic2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
images/pic3.png
Normal file
BIN
images/pic3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
images/pic4.png
Normal file
BIN
images/pic4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
BIN
images/pic5.png
Normal file
BIN
images/pic5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 951 KiB |
BIN
images/pic6.png
Normal file
BIN
images/pic6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 901 KiB |
@@ -25,7 +25,7 @@ class Plugin(PluginCode):
|
||||
if len(history) == history_size:
|
||||
history.pop(0)
|
||||
|
||||
history.append(event.file)
|
||||
history.append(event.file.fpath)
|
||||
|
||||
def load(self):
|
||||
self._manage_signals("register_command")
|
||||
@@ -60,6 +60,6 @@ class Handler:
|
||||
|
||||
view._on_uri_data_received(
|
||||
[
|
||||
history.pop().replace("file://", "")
|
||||
f"file://{history.pop()}"
|
||||
]
|
||||
)
|
||||
|
||||
@@ -23,7 +23,7 @@ class Plugin(PluginCode):
|
||||
def load(self):
|
||||
self._manage_signals("register_command")
|
||||
|
||||
def load(self):
|
||||
def unload(self):
|
||||
self._manage_signals("unregister_command")
|
||||
|
||||
def _manage_signals(self, action: str):
|
||||
|
||||
@@ -17,13 +17,22 @@ class Plugin(PluginCode):
|
||||
|
||||
|
||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||
if isinstance(event, Code_Event_Types.TextChangedEvent):
|
||||
event.file.check_file_on_disk()
|
||||
if not isinstance(event, Code_Event_Types.FocusedViewEvent): return
|
||||
event = Event_Factory.create_event(
|
||||
"get_file", buffer = event.view.get_buffer()
|
||||
)
|
||||
self.emit_to("files", event)
|
||||
|
||||
if event.file.is_deleted():
|
||||
file_is_deleted(event, self.emit)
|
||||
elif event.file.is_externally_modified():
|
||||
file_is_externally_modified(event, self.emit)
|
||||
file = event.response
|
||||
if not file: return
|
||||
if file.ftype == "buffer": return
|
||||
|
||||
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):
|
||||
...
|
||||
|
||||
@@ -10,23 +10,45 @@ from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
|
||||
|
||||
def file_is_deleted(event, emit):
|
||||
event.file.was_deleted = True
|
||||
def ask_yes_no(message):
|
||||
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(
|
||||
"file_externally_deleted",
|
||||
file = event.file,
|
||||
buffer = event.buffer
|
||||
file = file,
|
||||
buffer = file.buffer
|
||||
)
|
||||
emit(event)
|
||||
|
||||
|
||||
def file_is_externally_modified(event, emit):
|
||||
# event = Event_Factory.create_event(
|
||||
# "file_externally_modified",
|
||||
# file = event.file,
|
||||
# buffer = event.buffer
|
||||
# )
|
||||
# emit(event)
|
||||
def file_is_externally_modified(file, emit):
|
||||
event = Event_Factory.create_event(
|
||||
"file_externally_modified",
|
||||
file = file,
|
||||
buffer = file.buffer
|
||||
)
|
||||
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()
|
||||
|
||||
@@ -13,8 +13,6 @@ from gi.repository import Gtk
|
||||
|
||||
|
||||
def add_prettify_json(buffer, menu):
|
||||
menu.append(separator)
|
||||
|
||||
def on_prettify_json(menuitem, buffer):
|
||||
start_itr, \
|
||||
end_itr = buffer.get_start_iter(), buffer.get_end_iter()
|
||||
@@ -28,4 +26,4 @@ def add_prettify_json(buffer, menu):
|
||||
|
||||
item = Gtk.MenuItem(label = "Prettify JSON")
|
||||
item.connect("activate", on_prettify_json, buffer)
|
||||
menu.append(item)
|
||||
menu.append(item)
|
||||
3
plugins/code/event-watchers/tree_sitter/__init__.py
Normal file
3
plugins/code/event-watchers/tree_sitter/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Module
|
||||
"""
|
||||
3
plugins/code/event-watchers/tree_sitter/__main__.py
Normal file
3
plugins/code/event-watchers/tree_sitter/__main__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Pligin Package
|
||||
"""
|
||||
39
plugins/code/event-watchers/tree_sitter/compile.py
Normal file
39
plugins/code/event-watchers/tree_sitter/compile.py
Normal file
@@ -0,0 +1,39 @@
|
||||
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'])}")
|
||||
134
plugins/code/event-watchers/tree_sitter/languages/scripts/build.sh
Executable file
134
plugins/code/event-watchers/tree_sitter/languages/scripts/build.sh
Executable 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 "$@"
|
||||
3
plugins/code/event-watchers/tree_sitter/libs/__init__.py
Normal file
3
plugins/code/event-watchers/tree_sitter/libs/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Libs Module
|
||||
"""
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Python bindings to the Tree-sitter parsing library."""
|
||||
|
||||
from typing import Protocol as _Protocol
|
||||
|
||||
from ._binding import (
|
||||
Language,
|
||||
LogType,
|
||||
LookaheadIterator,
|
||||
Node,
|
||||
Parser,
|
||||
Point,
|
||||
Query,
|
||||
QueryCursor,
|
||||
QueryError,
|
||||
Range,
|
||||
Tree,
|
||||
TreeCursor,
|
||||
LANGUAGE_VERSION,
|
||||
MIN_COMPATIBLE_LANGUAGE_VERSION,
|
||||
)
|
||||
|
||||
LogType.__doc__ = "The type of a log message."
|
||||
|
||||
Point.__doc__ = "A position in a multi-line text document, in terms of rows and columns."
|
||||
Point.row.__doc__ = "The zero-based row of the document."
|
||||
Point.column.__doc__ = "The zero-based column of the document."
|
||||
|
||||
|
||||
class QueryPredicate(_Protocol):
|
||||
"""A custom query predicate that runs on a pattern."""
|
||||
def __call__(self, predicate, args, pattern_index, captures):
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
|
||||
predicate : str
|
||||
The name of the predicate.
|
||||
args : list[tuple[str, typing.Literal['capture', 'string']]]
|
||||
The arguments to the predicate.
|
||||
pattern_index : int
|
||||
The index of the pattern within the query.
|
||||
captures : dict[str, list[Node]]
|
||||
The captures contained in the pattern.
|
||||
|
||||
Returns
|
||||
-------
|
||||
``True`` if the predicate matches, ``False`` otherwise.
|
||||
|
||||
Tip
|
||||
---
|
||||
You don't need to create an actual class, just a function with this signature.
|
||||
"""
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Language",
|
||||
"LogType",
|
||||
"LookaheadIterator",
|
||||
"Node",
|
||||
"Parser",
|
||||
"Point",
|
||||
"Query",
|
||||
"QueryCursor",
|
||||
"QueryError",
|
||||
"QueryPredicate",
|
||||
"Range",
|
||||
"Tree",
|
||||
"TreeCursor",
|
||||
"LANGUAGE_VERSION",
|
||||
"MIN_COMPATIBLE_LANGUAGE_VERSION",
|
||||
]
|
||||
@@ -0,0 +1,416 @@
|
||||
from enum import IntEnum
|
||||
from collections.abc import ByteString, Callable, Iterator, Sequence
|
||||
from typing import Annotated, Any, Final, Literal, NamedTuple, Protocol, Self, final, overload
|
||||
from typing_extensions import deprecated
|
||||
|
||||
class _SupportsFileno(Protocol):
|
||||
def fileno(self) -> int: ...
|
||||
|
||||
class Point(NamedTuple):
|
||||
row: int
|
||||
column: int
|
||||
|
||||
class LogType(IntEnum):
|
||||
PARSE: int
|
||||
LEX: int
|
||||
|
||||
@final
|
||||
class Language:
|
||||
@overload
|
||||
@deprecated("int argument support is deprecated")
|
||||
def __init__(self, ptr: Annotated[int, "TSLanguage *"], /) -> None: ...
|
||||
@overload
|
||||
def __init__(self, ptr: Annotated[object, "TSLanguage *"], /) -> None: ...
|
||||
@property
|
||||
def name(self) -> str | None: ...
|
||||
@property
|
||||
def abi_version(self) -> int: ...
|
||||
@property
|
||||
def semantic_version(self) -> tuple[int, int, int] | None: ...
|
||||
@deprecated("Use abi_version instead")
|
||||
@property
|
||||
def version(self) -> int: ...
|
||||
@property
|
||||
def node_kind_count(self) -> int: ...
|
||||
@property
|
||||
def parse_state_count(self) -> int: ...
|
||||
@property
|
||||
def field_count(self) -> int: ...
|
||||
@property
|
||||
def supertypes(self) -> tuple[int, ...]: ...
|
||||
def subtypes(self, supertype: int, /) -> tuple[int, ...]: ...
|
||||
def node_kind_for_id(self, id: int, /) -> str | None: ...
|
||||
def id_for_node_kind(self, kind: str, named: bool, /) -> int | None: ...
|
||||
def node_kind_is_named(self, id: int, /) -> bool: ...
|
||||
def node_kind_is_visible(self, id: int, /) -> bool: ...
|
||||
def node_kind_is_supertype(self, id: int, /) -> bool: ...
|
||||
def field_name_for_id(self, field_id: int, /) -> str | None: ...
|
||||
def field_id_for_name(self, name: str, /) -> int | None: ...
|
||||
def next_state(self, state: int, id: int, /) -> int: ...
|
||||
def lookahead_iterator(self, state: int, /) -> LookaheadIterator | None: ...
|
||||
@deprecated("Use the Query() constructor instead")
|
||||
def query(self, source: str, /) -> Query: ...
|
||||
def copy(self) -> Language: ...
|
||||
def __repr__(self) -> str: ...
|
||||
def __eq__(self, other: Any, /) -> bool: ...
|
||||
def __ne__(self, other: Any, /) -> bool: ...
|
||||
def __hash__(self) -> int: ...
|
||||
def __copy__(self) -> Language: ...
|
||||
|
||||
@final
|
||||
class Node:
|
||||
@property
|
||||
def id(self) -> int: ...
|
||||
@property
|
||||
def kind_id(self) -> int: ...
|
||||
@property
|
||||
def grammar_id(self) -> int: ...
|
||||
@property
|
||||
def grammar_name(self) -> str: ...
|
||||
@property
|
||||
def type(self) -> str: ...
|
||||
@property
|
||||
def is_named(self) -> bool: ...
|
||||
@property
|
||||
def is_extra(self) -> bool: ...
|
||||
@property
|
||||
def has_changes(self) -> bool: ...
|
||||
@property
|
||||
def has_error(self) -> bool: ...
|
||||
@property
|
||||
def is_error(self) -> bool: ...
|
||||
@property
|
||||
def parse_state(self) -> int: ...
|
||||
@property
|
||||
def next_parse_state(self) -> int: ...
|
||||
@property
|
||||
def is_missing(self) -> bool: ...
|
||||
@property
|
||||
def start_byte(self) -> int: ...
|
||||
@property
|
||||
def end_byte(self) -> int: ...
|
||||
@property
|
||||
def byte_range(self) -> tuple[int, int]: ...
|
||||
@property
|
||||
def range(self) -> Range: ...
|
||||
@property
|
||||
def start_point(self) -> Point: ...
|
||||
@property
|
||||
def end_point(self) -> Point: ...
|
||||
@property
|
||||
def children(self) -> list[Node]: ...
|
||||
@property
|
||||
def child_count(self) -> int: ...
|
||||
@property
|
||||
def named_children(self) -> list[Node]: ...
|
||||
@property
|
||||
def named_child_count(self) -> int: ...
|
||||
@property
|
||||
def parent(self) -> Node | None: ...
|
||||
@property
|
||||
def next_sibling(self) -> Node | None: ...
|
||||
@property
|
||||
def prev_sibling(self) -> Node | None: ...
|
||||
@property
|
||||
def next_named_sibling(self) -> Node | None: ...
|
||||
@property
|
||||
def prev_named_sibling(self) -> Node | None: ...
|
||||
@property
|
||||
def descendant_count(self) -> int: ...
|
||||
@property
|
||||
def text(self) -> bytes | None: ...
|
||||
def walk(self) -> TreeCursor: ...
|
||||
def edit(
|
||||
self,
|
||||
start_byte: int,
|
||||
old_end_byte: int,
|
||||
new_end_byte: int,
|
||||
start_point: Point | tuple[int, int],
|
||||
old_end_point: Point | tuple[int, int],
|
||||
new_end_point: Point | tuple[int, int],
|
||||
) -> None: ...
|
||||
def child(self, index: int, /) -> Node | None: ...
|
||||
def named_child(self, index: int, /) -> Node | None: ...
|
||||
def first_child_for_byte(self, byte: int, /) -> Node | None: ...
|
||||
def first_named_child_for_byte(self, byte: int, /) -> Node | None: ...
|
||||
def child_by_field_id(self, id: int, /) -> Node | None: ...
|
||||
def child_by_field_name(self, name: str, /) -> Node | None: ...
|
||||
def child_with_descendant(self, descendant: Node, /) -> Node | None: ...
|
||||
def children_by_field_id(self, id: int, /) -> list[Node]: ...
|
||||
def children_by_field_name(self, name: str, /) -> list[Node]: ...
|
||||
def field_name_for_child(self, child_index: int, /) -> str | None: ...
|
||||
def field_name_for_named_child(self, child_index: int, /) -> str | None: ...
|
||||
def descendant_for_byte_range(
|
||||
self,
|
||||
start_byte: int,
|
||||
end_byte: int,
|
||||
/,
|
||||
) -> Node | None: ...
|
||||
def named_descendant_for_byte_range(
|
||||
self,
|
||||
start_byte: int,
|
||||
end_byte: int,
|
||||
/,
|
||||
) -> Node | None: ...
|
||||
def descendant_for_point_range(
|
||||
self,
|
||||
start_point: Point | tuple[int, int],
|
||||
end_point: Point | tuple[int, int],
|
||||
/,
|
||||
) -> Node | None: ...
|
||||
def named_descendant_for_point_range(
|
||||
self,
|
||||
start_point: Point | tuple[int, int],
|
||||
end_point: Point | tuple[int, int],
|
||||
/,
|
||||
) -> Node | None: ...
|
||||
def __repr__(self) -> str: ...
|
||||
def __str__(self) -> str: ...
|
||||
def __eq__(self, other: Any, /) -> bool: ...
|
||||
def __ne__(self, other: Any, /) -> bool: ...
|
||||
def __hash__(self) -> int: ...
|
||||
|
||||
@final
|
||||
class Tree:
|
||||
@property
|
||||
def root_node(self) -> Node: ...
|
||||
@property
|
||||
def included_ranges(self) -> list[Range]: ...
|
||||
@property
|
||||
def language(self) -> Language: ...
|
||||
def root_node_with_offset(
|
||||
self,
|
||||
offset_bytes: int,
|
||||
offset_extent: Point | tuple[int, int],
|
||||
/,
|
||||
) -> Node | None: ...
|
||||
def copy(self) -> Tree: ...
|
||||
def edit(
|
||||
self,
|
||||
start_byte: int,
|
||||
old_end_byte: int,
|
||||
new_end_byte: int,
|
||||
start_point: Point | tuple[int, int],
|
||||
old_end_point: Point | tuple[int, int],
|
||||
new_end_point: Point | tuple[int, int],
|
||||
) -> None: ...
|
||||
def walk(self) -> TreeCursor: ...
|
||||
def changed_ranges(self, new_tree: Tree, /) -> list[Range]: ...
|
||||
def print_dot_graph(self, file: _SupportsFileno, /) -> None: ...
|
||||
def __copy__(self) -> Tree: ...
|
||||
|
||||
@final
|
||||
class TreeCursor:
|
||||
@property
|
||||
def node(self) -> Node | None: ...
|
||||
@property
|
||||
def field_id(self) -> int | None: ...
|
||||
@property
|
||||
def field_name(self) -> str | None: ...
|
||||
@property
|
||||
def depth(self) -> int: ...
|
||||
@property
|
||||
def descendant_index(self) -> int: ...
|
||||
def copy(self) -> TreeCursor: ...
|
||||
def reset(self, node: Node, /) -> None: ...
|
||||
def reset_to(self, cursor: TreeCursor, /) -> None: ...
|
||||
def goto_first_child(self) -> bool: ...
|
||||
def goto_last_child(self) -> bool: ...
|
||||
def goto_parent(self) -> bool: ...
|
||||
def goto_next_sibling(self) -> bool: ...
|
||||
def goto_previous_sibling(self) -> bool: ...
|
||||
def goto_descendant(self, index: int, /) -> None: ...
|
||||
def goto_first_child_for_byte(self, byte: int, /) -> int | None: ...
|
||||
def goto_first_child_for_point(self, point: Point | tuple[int, int], /) -> int | None: ...
|
||||
def __copy__(self) -> TreeCursor: ...
|
||||
|
||||
@final
|
||||
class Parser:
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
language: Language | None = None,
|
||||
*,
|
||||
included_ranges: Sequence[Range] | None = None,
|
||||
logger: Callable[[LogType, str], None] | None = None,
|
||||
) -> None: ...
|
||||
@deprecated("timeout_micros is deprecated")
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
language: Language | None = None,
|
||||
*,
|
||||
included_ranges: Sequence[Range] | None = None,
|
||||
timeout_micros: int | None = None,
|
||||
logger: Callable[[LogType, str], None] | None = None,
|
||||
) -> None: ...
|
||||
@property
|
||||
def language(self) -> Language | None: ...
|
||||
@language.setter
|
||||
def language(self, language: Language) -> None: ...
|
||||
@language.deleter
|
||||
def language(self) -> None: ...
|
||||
@property
|
||||
def included_ranges(self) -> list[Range]: ...
|
||||
@included_ranges.setter
|
||||
def included_ranges(self, ranges: Sequence[Range]) -> None: ...
|
||||
@included_ranges.deleter
|
||||
def included_ranges(self) -> None: ...
|
||||
@deprecated("Use the progress_callback in parse()")
|
||||
@property
|
||||
def timeout_micros(self) -> int: ...
|
||||
@deprecated("Use the progress_callback in parse()")
|
||||
@timeout_micros.setter
|
||||
def timeout_micros(self, timeout: int) -> None: ...
|
||||
@deprecated("Use the progress_callback in parse()")
|
||||
@timeout_micros.deleter
|
||||
def timeout_micros(self) -> None: ...
|
||||
@property
|
||||
def logger(self) -> Callable[[LogType, str], None] | None: ...
|
||||
@logger.setter
|
||||
def logger(self, logger: Callable[[LogType, str], None]) -> None: ...
|
||||
@logger.deleter
|
||||
def logger(self) -> None: ...
|
||||
@overload
|
||||
def parse(
|
||||
self,
|
||||
source: ByteString,
|
||||
/,
|
||||
old_tree: Tree | None = None,
|
||||
encoding: Literal["utf8", "utf16", "utf16le", "utf16be"] = "utf8",
|
||||
) -> Tree: ...
|
||||
@overload
|
||||
def parse(
|
||||
self,
|
||||
read_callback: Callable[[int, Point], ByteString | None],
|
||||
/,
|
||||
old_tree: Tree | None = None,
|
||||
encoding: Literal["utf8", "utf16", "utf16le", "utf16be"] = "utf8",
|
||||
progress_callback: Callable[[int, bool], bool] | None = None,
|
||||
) -> Tree: ...
|
||||
def reset(self) -> None: ...
|
||||
def print_dot_graphs(self, file: _SupportsFileno | None, /) -> None: ...
|
||||
|
||||
class QueryError(ValueError): ...
|
||||
|
||||
class QueryPredicate(Protocol):
|
||||
def __call__(
|
||||
self,
|
||||
predicate: str,
|
||||
args: list[tuple[str, Literal["capture", "string"]]],
|
||||
pattern_index: int,
|
||||
captures: dict[str, list[Node]],
|
||||
) -> bool: ...
|
||||
|
||||
@final
|
||||
class Query:
|
||||
def __new__(cls, language: Language, source: str, /) -> Self: ...
|
||||
def pattern_count(self) -> int: ...
|
||||
def capture_count(self) -> int: ...
|
||||
def string_count(self) -> int: ...
|
||||
def start_byte_for_pattern(self, index: int, /) -> int: ...
|
||||
def end_byte_for_pattern(self, index: int, /) -> int: ...
|
||||
def is_pattern_rooted(self, index: int, /) -> bool: ...
|
||||
def is_pattern_non_local(self, index: int, /) -> bool: ...
|
||||
def is_pattern_guaranteed_at_step(self, index: int, /) -> bool: ...
|
||||
def capture_name(self, index: int, /) -> str: ...
|
||||
def capture_quantifier(
|
||||
self,
|
||||
pattern_index: int,
|
||||
capture_index: int,
|
||||
/
|
||||
) -> Literal["", "?", "*", "+"]: ...
|
||||
def string_value(self, index: int, /) -> str: ...
|
||||
def disable_capture(self, name: str, /) -> None: ...
|
||||
def disable_pattern(self, index: int, /) -> None: ...
|
||||
def pattern_settings(self, index: int, /) -> dict[str, str | None]: ...
|
||||
def pattern_assertions(self, index: int, /) -> dict[str, tuple[str | None, bool]]: ...
|
||||
|
||||
@final
|
||||
class QueryCursor:
|
||||
@overload
|
||||
def __init__(self, query: Query, *, match_limit: int = 0xFFFFFFFF) -> None: ...
|
||||
@deprecated("timeout_micros is deprecated")
|
||||
@overload
|
||||
def __init__(
|
||||
self,
|
||||
query: Query,
|
||||
*,
|
||||
match_limit: int = 0xFFFFFFFF,
|
||||
timeout_micros: int = 0
|
||||
) -> None: ...
|
||||
@property
|
||||
def match_limit(self) -> int: ...
|
||||
@match_limit.setter
|
||||
def match_limit(self, limit: int) -> None: ...
|
||||
@match_limit.deleter
|
||||
def match_limit(self) -> None: ...
|
||||
@deprecated("Use the progress_callback in matches() or captures()")
|
||||
@property
|
||||
def timeout_micros(self) -> int: ...
|
||||
@deprecated("Use the progress_callback in matches() or captures()")
|
||||
@timeout_micros.setter
|
||||
def timeout_micros(self, timeout: int) -> None: ...
|
||||
@property
|
||||
def did_exceed_match_limit(self) -> bool: ...
|
||||
def set_max_start_depth(self, depth: int, /) -> None: ...
|
||||
def set_byte_range(self, start: int, end: int, /) -> None: ...
|
||||
def set_point_range(
|
||||
self,
|
||||
start: Point | tuple[int, int],
|
||||
end: Point | tuple[int, int],
|
||||
/,
|
||||
) -> None: ...
|
||||
def captures(
|
||||
self,
|
||||
node: Node,
|
||||
predicate: QueryPredicate | None = None,
|
||||
progress_callback: Callable[[int], bool] | None = None,
|
||||
/,
|
||||
) -> dict[str, list[Node]]: ...
|
||||
def matches(
|
||||
self,
|
||||
node: Node,
|
||||
predicate: QueryPredicate | None = None,
|
||||
progress_callback: Callable[[int], bool] | None = None,
|
||||
/,
|
||||
) -> list[tuple[int, dict[str, list[Node]]]]: ...
|
||||
|
||||
@final
|
||||
class LookaheadIterator(Iterator[tuple[int, str]]):
|
||||
@property
|
||||
def language(self) -> Language: ...
|
||||
@property
|
||||
def current_symbol(self) -> int: ...
|
||||
@property
|
||||
def current_symbol_name(self) -> str: ...
|
||||
def reset(self, state: int, /, language: Language | None = None) -> bool: ...
|
||||
def names(self) -> list[str]: ...
|
||||
def symbols(self) -> list[int]: ...
|
||||
def __next__(self) -> tuple[int, str]: ...
|
||||
|
||||
@final
|
||||
class Range:
|
||||
def __init__(
|
||||
self,
|
||||
start_point: Point | tuple[int, int],
|
||||
end_point: Point | tuple[int, int],
|
||||
start_byte: int,
|
||||
end_byte: int,
|
||||
) -> None: ...
|
||||
@property
|
||||
def start_point(self) -> Point: ...
|
||||
@property
|
||||
def end_point(self) -> Point: ...
|
||||
@property
|
||||
def start_byte(self) -> int: ...
|
||||
@property
|
||||
def end_byte(self) -> int: ...
|
||||
def __eq__(self, other: Any, /) -> bool: ...
|
||||
def __ne__(self, other: Any, /) -> bool: ...
|
||||
def __repr__(self) -> str: ...
|
||||
def __hash__(self) -> int: ...
|
||||
|
||||
LANGUAGE_VERSION: Final[int]
|
||||
|
||||
MIN_COMPATIBLE_LANGUAGE_VERSION: Final[int]
|
||||
8
plugins/code/event-watchers/tree_sitter/manifest.json
Normal file
8
plugins/code/event-watchers/tree_sitter/manifest.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "Tree-sitter",
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"support": "",
|
||||
"pre_launch": true,
|
||||
"requests": {}
|
||||
}
|
||||
47
plugins/code/event-watchers/tree_sitter/plugin.py
Normal file
47
plugins/code/event-watchers/tree_sitter/plugin.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
from plugins.plugin_types import PluginCode
|
||||
|
||||
from .tree_sitter import Parser, get_parser
|
||||
|
||||
|
||||
|
||||
class Plugin(PluginCode):
|
||||
def __init__(self):
|
||||
super(Plugin, self).__init__()
|
||||
|
||||
|
||||
def _controller_message(self, event: Code_Event_Types.CodeEvent):
|
||||
if isinstance(event, Code_Event_Types.TextChangedEvent):
|
||||
if not hasattr(event.file, "tree_sitter"):
|
||||
parser = get_parser( event.file.ftype )
|
||||
if not parser: return
|
||||
|
||||
event.file.tree_sitter = parser
|
||||
|
||||
buffer = event.file.buffer
|
||||
start_itr, \
|
||||
end_itr = buffer.get_bounds()
|
||||
text = buffer.get_text(start_itr, end_itr, True)
|
||||
|
||||
tree = event.file.tree_sitter.parse( text.encode("UTF-8") )
|
||||
event.file.ast = tree
|
||||
|
||||
# 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 unload(self):
|
||||
...
|
||||
|
||||
def run(self):
|
||||
...
|
||||
57
plugins/code/event-watchers/tree_sitter/tree_sitter.py
Normal file
57
plugins/code/event-watchers/tree_sitter/tree_sitter.py
Normal 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 in LANGUAGES: return
|
||||
|
||||
parser = Parser()
|
||||
parser.language = language
|
||||
|
||||
return parser
|
||||
|
||||
@@ -11,7 +11,7 @@ from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
|
||||
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",
|
||||
lang_id = "gdscript",
|
||||
lang_config = config,
|
||||
handler = GodotHandler
|
||||
handler = GDScriptHandler
|
||||
)
|
||||
self.emit_to("lsp_manager", event)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
from .python import PythonHandler
|
||||
from .gdscript import GDScriptHandler
|
||||
@@ -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."""
|
||||
...
|
||||
@@ -3,6 +3,7 @@
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"support": "",
|
||||
"pre_launch": true,
|
||||
"autoload": false,
|
||||
"requests": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
@@ -25,8 +28,19 @@ class Plugin(PluginCode):
|
||||
|
||||
self.register_controller("tabs", self.tabs_controller)
|
||||
|
||||
code_container.add( self.tabs_controller.tabs_widget )
|
||||
code_container.reorder_child(self.tabs_controller.tabs_widget, 0)
|
||||
scrolled_win = Gtk.ScrolledWindow()
|
||||
viewport = Gtk.Viewport()
|
||||
|
||||
scrolled_win.set_overlay_scrolling(False)
|
||||
scrolled_win.set_size_request(-1, 50)
|
||||
|
||||
viewport.add( self.tabs_controller.tabs_widget )
|
||||
scrolled_win.add( viewport )
|
||||
code_container.add( scrolled_win )
|
||||
code_container.reorder_child(scrolled_win, 0)
|
||||
|
||||
viewport.show()
|
||||
scrolled_win.show()
|
||||
|
||||
event = Event_Factory.create_event("get_files")
|
||||
self.emit_to("files", event)
|
||||
@@ -36,7 +50,14 @@ class Plugin(PluginCode):
|
||||
def unload(self):
|
||||
self.unregister_controller("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 = None
|
||||
|
||||
@@ -27,7 +27,7 @@ class TabsWidget(Gtk.Notebook):
|
||||
|
||||
|
||||
def _setup_styling(self):
|
||||
self.set_scrollable(True)
|
||||
...
|
||||
|
||||
def _setup_signals(self):
|
||||
self.connect("page-added", self._page_added)
|
||||
@@ -67,6 +67,7 @@ class TabsWidget(Gtk.Notebook):
|
||||
)
|
||||
|
||||
self.emit(event)
|
||||
self._scroll_to_center(tab)
|
||||
|
||||
def _bind_tab_menu(self, tab, page_widget):
|
||||
def do_context_menu(tab, eve, page_widget):
|
||||
@@ -81,6 +82,21 @@ class TabsWidget(Gtk.Notebook):
|
||||
page_widget
|
||||
)
|
||||
|
||||
def _scroll_to_center(self, tab):
|
||||
scrolled_win = self.get_parent().get_parent()
|
||||
alloc = tab.get_allocation()
|
||||
tab_x = alloc.x
|
||||
tab_width = alloc.width
|
||||
view_width = scrolled_win.get_allocated_width()
|
||||
target = tab_x + tab_width / 2 - view_width / 2
|
||||
adj = scrolled_win.get_hadjustment()
|
||||
lower = adj.get_lower()
|
||||
upper = adj.get_upper()
|
||||
page_size = adj.get_page_size()
|
||||
target = max(lower, min(target, upper - page_size))
|
||||
|
||||
adj.set_value(target)
|
||||
|
||||
def create_menu(self, page_widget) -> Gtk.Menu:
|
||||
context_menu = Gtk.Menu()
|
||||
close_submenu = Gtk.Menu()
|
||||
@@ -130,6 +146,7 @@ class TabsWidget(Gtk.Notebook):
|
||||
self.page_num(page_widget)
|
||||
)
|
||||
self.handler_unblock(self.switch_page_id)
|
||||
self._scroll_to_center(tab)
|
||||
|
||||
break
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ class ListBox(Gtk.ListBox):
|
||||
|
||||
def _row_activated(self, list_box, row = None):
|
||||
row = self.get_selected_row()
|
||||
if not row: return
|
||||
file = row.get_children()[0].file
|
||||
|
||||
event = Event_Factory.create_event(
|
||||
@@ -70,24 +71,62 @@ class ListBox(Gtk.ListBox):
|
||||
|
||||
def search_changed(self, entry):
|
||||
self.search_buffer_names(entry)
|
||||
|
||||
for row in self.get_children():
|
||||
if not row.is_visible(): continue
|
||||
self.select_row(row)
|
||||
break
|
||||
|
||||
def fuzzy_score(self, query, text):
|
||||
query = query.lower()
|
||||
text = text.lower()
|
||||
score = 0
|
||||
q_idx = 0
|
||||
|
||||
for char in text:
|
||||
if q_idx < len(query) and char == query[q_idx]:
|
||||
score += 1
|
||||
q_idx += 1
|
||||
|
||||
return score if q_idx == len(query) else 0
|
||||
|
||||
|
||||
def search_buffer_names(self, entry):
|
||||
text = entry.get_text()
|
||||
if not text:
|
||||
for row in self.get_children():
|
||||
row.show()
|
||||
|
||||
return
|
||||
query = entry.get_text().lower()
|
||||
rows = []
|
||||
|
||||
for row in self.get_children():
|
||||
child = row.get_children()[0]
|
||||
child = row.get_children()[0]
|
||||
label_text = child.get_label()
|
||||
score = self.fuzzy_score(query, label_text) if query else 1
|
||||
child.label_text = label_text if not hasattr(child, "label_text") else child.label_text
|
||||
|
||||
row.show() \
|
||||
if text in child.get_label() else \
|
||||
row.hide()
|
||||
rows.append((score, row, child, label_text))
|
||||
|
||||
rows.sort(key = lambda x: x[0], reverse = True)
|
||||
for score, row, child, label_text in rows:
|
||||
if query and score > 0:
|
||||
highlighted = self.highlight_match(label_text, query)
|
||||
child.set_markup(highlighted)
|
||||
row.show()
|
||||
elif not query:
|
||||
child.set_label(child.label_text)
|
||||
row.show()
|
||||
else:
|
||||
row.hide()
|
||||
|
||||
def highlight_match(self, text, query):
|
||||
i = 0
|
||||
result = ""
|
||||
|
||||
for char in text:
|
||||
if i < len(query) and char.lower() == query[i].lower():
|
||||
result += f"<b><i><u>{char}</u></i></b>"
|
||||
i += 1
|
||||
else:
|
||||
result += char
|
||||
|
||||
return result
|
||||
|
||||
def activate_row(self):
|
||||
self._row_activated(self)
|
||||
@@ -96,30 +135,44 @@ class ListBox(Gtk.ListBox):
|
||||
raise TelescopeListBoxException("ListBox must have 'set_buffer' monkey patched...")
|
||||
|
||||
def move_row_selection_up(self):
|
||||
row = self.get_selected_row()
|
||||
next_row = self.get_row_at_index(row.get_index() - 1)
|
||||
row = self.get_selected_row()
|
||||
if not row: return
|
||||
|
||||
if not next_row:
|
||||
next_row = self.get_row_at_index(
|
||||
len( self.get_children() ) - 1
|
||||
)
|
||||
rows = [r for r in self.get_children() if r.is_visible()]
|
||||
if not rows: return
|
||||
|
||||
self.select_row(next_row)
|
||||
try:
|
||||
idx = rows.index(row)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
next_idx = (idx - 1) % len(rows)
|
||||
self.select_row(rows[next_idx])
|
||||
|
||||
def move_row_selection_down(self):
|
||||
row = self.get_selected_row()
|
||||
next_row = self.get_row_at_index(row.get_index() + 1)
|
||||
row = self.get_selected_row()
|
||||
if not row: return
|
||||
|
||||
if not next_row:
|
||||
next_row = self.get_row_at_index(0)
|
||||
rows = [r for r in self.get_children() if r.is_visible()]
|
||||
if not rows: return
|
||||
|
||||
self.select_row(next_row)
|
||||
try:
|
||||
idx = rows.index(row)
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
next_idx = (idx + 1) % len(rows)
|
||||
self.select_row(rows[next_idx])
|
||||
|
||||
def add_row(self, file):
|
||||
label = Gtk.Label(label = file.fname)
|
||||
row = Gtk.ListBoxRow()
|
||||
label = Gtk.Label(label = file.fname)
|
||||
label.file = file
|
||||
label.show()
|
||||
self.add(label)
|
||||
|
||||
row.add(label)
|
||||
row.show_all()
|
||||
|
||||
self.add(row)
|
||||
|
||||
def remove_row(self, event):
|
||||
for row in self.get_children():
|
||||
|
||||
@@ -18,16 +18,12 @@ class BaseControllerMixin:
|
||||
files = []
|
||||
|
||||
for arg in unknownargs + [args.new_tab,]:
|
||||
if os.path.isdir( arg.replace("file://", "") ):
|
||||
files.append( f"DIR|{arg.replace('file://', '')}" )
|
||||
continue
|
||||
if os.path.isfile(arg):
|
||||
files.append(f"{arg}")
|
||||
|
||||
# NOTE: If passing line number with file split against :
|
||||
if os.path.isfile( arg.replace("file://", "").split(":")[0] ):
|
||||
files.append( f"FILE|{arg.replace('file://', '')}" )
|
||||
continue
|
||||
|
||||
logger.info(f"Not a File: {arg}")
|
||||
if os.path.isdir(arg):
|
||||
message = f"DIR|{arg}"
|
||||
ipc_server.send_ipc_message(message)
|
||||
|
||||
if not files: return
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ gi.require_version('GtkSource', '4')
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
from libs.dto.states import SourceViewStates
|
||||
|
||||
|
||||
|
||||
@@ -17,4 +18,6 @@ def execute(
|
||||
**kwargs
|
||||
):
|
||||
logger.debug("Command: Line Down")
|
||||
if not view.state == SourceViewStates.INSERT: return
|
||||
|
||||
view.emit("move-lines", True)
|
||||
|
||||
@@ -8,6 +8,7 @@ gi.require_version('GtkSource', '4')
|
||||
from gi.repository import GtkSource
|
||||
|
||||
# Application imports
|
||||
from libs.dto.states import SourceViewStates
|
||||
|
||||
|
||||
|
||||
@@ -17,4 +18,6 @@ def execute(
|
||||
**kwargs
|
||||
):
|
||||
logger.debug("Command: Line Up")
|
||||
if not view.state == SourceViewStates.INSERT: return
|
||||
|
||||
view.emit("move-lines", False)
|
||||
|
||||
@@ -56,10 +56,28 @@ class MarkerManager(MarkSupportMixin):
|
||||
continue
|
||||
|
||||
if has_selection:
|
||||
caret_itr = buffer.get_iter_at_mark(end_mark)
|
||||
start_itr = buffer.get_iter_at_mark(start_mark)
|
||||
is_left_edge = caret_itr.compare(start_itr) <= 0
|
||||
is_right_edge = not is_left_edge
|
||||
can_move = (
|
||||
(is_forward and is_right_edge) or
|
||||
(not is_forward and is_left_edge)
|
||||
)
|
||||
|
||||
self.collapse_selection(buffer, mark_hash, start_mark, end_mark, is_forward)
|
||||
if mode == "word":
|
||||
if not can_move: continue
|
||||
|
||||
itr = caret_itr
|
||||
self._move_iter(buffer, itr, mode, is_forward)
|
||||
buffer.move_mark(start_mark, itr)
|
||||
buffer.move_mark(end_mark, itr)
|
||||
|
||||
continue
|
||||
|
||||
# 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)
|
||||
|
||||
buffer.move_mark(start_mark, end_itr)
|
||||
@@ -81,17 +99,50 @@ class MarkerManager(MarkSupportMixin):
|
||||
left = end_itr
|
||||
right = start_itr
|
||||
|
||||
# If moving forward → collapse to right edge
|
||||
# If moving forward -> collapse to right edge
|
||||
collapse_itr = right if is_forward else left
|
||||
|
||||
buffer.move_mark(start_mark, collapse_itr)
|
||||
buffer.move_mark(end_mark, collapse_itr)
|
||||
|
||||
def move_word_snake_case(self, itr: Gtk.TextIter, count: int):
|
||||
def is_word(ch):
|
||||
return ch and (ch.isalnum() or ch == "_")
|
||||
|
||||
def step(fwd):
|
||||
return itr.forward_cursor_position() if fwd else itr.backward_cursor_position()
|
||||
|
||||
def peek(fwd):
|
||||
if fwd: return itr.get_char()
|
||||
tmp = itr.copy()
|
||||
return tmp.backward_cursor_position() and tmp.get_char()
|
||||
|
||||
def walk(fwd, cond):
|
||||
while True:
|
||||
ch = peek(fwd)
|
||||
if not cond(ch): break
|
||||
if not step(fwd): return False
|
||||
|
||||
return True
|
||||
|
||||
fwd = count > 0
|
||||
|
||||
for _ in range(abs(count)):
|
||||
ch = itr.get_char() if fwd else peek(False)
|
||||
|
||||
if is_word(ch):
|
||||
# inside word
|
||||
if not walk(fwd, is_word): return
|
||||
else:
|
||||
# in separators -> skip them, then the word
|
||||
if not walk(fwd, lambda c: not is_word(c)): return
|
||||
if not walk(fwd, is_word): return
|
||||
|
||||
def _move_iter(self, buffer, itr_, mode: str, is_forward: bool):
|
||||
if mode == "char":
|
||||
itr_.forward_char() if is_forward else itr_.backward_char()
|
||||
elif mode == "word":
|
||||
itr_.forward_word_end() if is_forward else itr_.backward_word_start()
|
||||
self.move_word_snake_case(itr_, 1 if is_forward else -1)
|
||||
elif mode == "line":
|
||||
line = itr_.get_line()
|
||||
offset = itr_.get_line_offset()
|
||||
|
||||
@@ -57,6 +57,7 @@ class SourceViewSignalMapper:
|
||||
"key-release-event": self._key_release_event,
|
||||
"button-press-event": self._button_press_event,
|
||||
"button-release-event": self._button_release_event,
|
||||
"scroll-event": self._scroll_event,
|
||||
"populate-popup": self._populate_popup
|
||||
}
|
||||
|
||||
@@ -81,5 +82,8 @@ class SourceViewSignalMapper:
|
||||
def _button_release_event(self, source_view: SourceView, eve):
|
||||
return self.state_manager.handle_button_release_event(source_view, eve)
|
||||
|
||||
def _populate_popup(self, source_view, menu):
|
||||
def _scroll_event(self, source_view: SourceView, eve):
|
||||
return self.state_manager.handle_scroll_event(source_view, eve)
|
||||
|
||||
def _populate_popup(self, source_view: SourceView, menu):
|
||||
return self.state_manager.handle_populate_popup(source_view, menu, self.emit)
|
||||
|
||||
@@ -53,8 +53,15 @@ class SourceViewStateManager:
|
||||
def handle_button_release_event(self, source_view, eve):
|
||||
return self.states[source_view.state].button_release_event(source_view, eve)
|
||||
|
||||
def handle_scroll_event(self, source_view, eve):
|
||||
return self.states[source_view.state].scroll_event(
|
||||
source_view, eve, self.key_mapper
|
||||
)
|
||||
|
||||
def handle_populate_popup(self, source_view, menu, emit):
|
||||
return self.states[source_view.state].populate_popup(source_view, menu, emit)
|
||||
return self.states[source_view.state].populate_popup(
|
||||
source_view, menu, emit
|
||||
)
|
||||
|
||||
def _handle_multi_insert_toggle(self, source_view, eve):
|
||||
is_control = self.key_mapper.is_control(eve)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gdk
|
||||
|
||||
# Application imports
|
||||
from libs.event_factory import Event_Factory, Code_Event_Types
|
||||
@@ -79,6 +82,29 @@ class SourceViewsBaseState:
|
||||
|
||||
return True if not response else response
|
||||
|
||||
def scroll_event(self, source_view, eve, key_mapper):
|
||||
is_control = key_mapper.is_control(eve)
|
||||
|
||||
if not is_control: return
|
||||
|
||||
if eve.direction == Gdk.ScrollDirection.SMOOTH:
|
||||
has_deltas, dx, dy = eve.get_scroll_deltas()
|
||||
if not has_deltas: return False
|
||||
|
||||
if dy < 0:
|
||||
source_view.command.exec("zoom_in")
|
||||
elif dy > 0:
|
||||
source_view.command.exec("zoom_out")
|
||||
|
||||
return True
|
||||
|
||||
if eve.direction == Gdk.ScrollDirection.UP:
|
||||
source_view.command.exec("zoom_in")
|
||||
elif eve.direction == Gdk.ScrollDirection.DOWN:
|
||||
source_view.command.exec("zoom_out")
|
||||
|
||||
return True
|
||||
|
||||
def populate_popup(self, source_view, menu, emit):
|
||||
buffer = source_view.get_buffer()
|
||||
event = Event_Factory.create_event(
|
||||
|
||||
@@ -61,7 +61,6 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
|
||||
self.marker_manager.apply_to_marks(buffer, replace_word)
|
||||
return True
|
||||
|
||||
|
||||
def move_cursor(self, source_view, step, count, is_selection, emit):
|
||||
is_forward = count > 0
|
||||
buffer = source_view.get_buffer()
|
||||
@@ -78,6 +77,8 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
|
||||
|
||||
self._signal_cursor_moved(source_view, emit)
|
||||
|
||||
return False
|
||||
|
||||
def key_press_event(self, source_view, event, key_mapper):
|
||||
char = key_mapper.get_raw_keyname(event).upper()
|
||||
self.is_control = key_mapper.is_control(event)
|
||||
|
||||
@@ -106,7 +106,6 @@ class MarkSupportMixin:
|
||||
name = f"multi-insert-end-{hash}",
|
||||
left_gravity = False
|
||||
)
|
||||
# left_gravity = True
|
||||
|
||||
buffer.add_mark(start_mark, target_iter)
|
||||
buffer.add_mark(end_mark, target_iter)
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
#gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
#from gi.repository import Gdk
|
||||
|
||||
|
||||
# Application imports
|
||||
|
||||
@@ -12,25 +15,41 @@ from gi.repository import Gtk
|
||||
class SourceViewDnDMixin:
|
||||
|
||||
def _set_up_dnd(self):
|
||||
PLAIN_TEXT_TARGET_TYPE = 70
|
||||
URI_TARGET_TYPE = 80
|
||||
text_target = Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE)
|
||||
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
|
||||
targets = [ text_target, uri_target ]
|
||||
URI_TARGET_TYPE = 10
|
||||
PLAIN_TEXT_TARGET_TYPE = 50
|
||||
|
||||
uri_target = Gtk.TargetEntry.new(
|
||||
'text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE
|
||||
)
|
||||
text_target = Gtk.TargetEntry.new(
|
||||
'text/plain', Gtk.TargetFlags(0), PLAIN_TEXT_TARGET_TYPE
|
||||
)
|
||||
targets = Gtk.TargetList.new([ uri_target, text_target ])
|
||||
|
||||
self.drag_dest_set_target_list(targets)
|
||||
|
||||
def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
||||
if info == 70: return
|
||||
def _on_drag_data_received(
|
||||
self, widget, drag_context, x, y, data, info, time
|
||||
):
|
||||
target = data.get_target().name()
|
||||
|
||||
if info == 80:
|
||||
if (info == 10) or (target == "text/uri-list"):
|
||||
uris = data.get_uris()
|
||||
|
||||
if not uris:
|
||||
uris = data.get_text().split("\n")
|
||||
|
||||
self._on_uri_data_received(uris)
|
||||
|
||||
drag_context.finish(True, False, time)
|
||||
|
||||
return
|
||||
elif (info == 50) or (target == "text/plain"):
|
||||
...
|
||||
else:
|
||||
logger.info(f"DnD Dropped File Type: {target}")
|
||||
|
||||
drag_context.finish(False, False, time)
|
||||
|
||||
def _on_uri_data_received(self, uris: list[str]):
|
||||
uris = self.command.filter_out_loaded_files(uris)
|
||||
if not uris: return
|
||||
|
||||
@@ -120,21 +120,7 @@ class SourceFile(GtkSource.File):
|
||||
|
||||
return gfile
|
||||
|
||||
|
||||
def load_path(self, gfile: Gio.File):
|
||||
if not gfile: return
|
||||
|
||||
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) \
|
||||
.replace("application/", "") \
|
||||
.replace("text/", "") \
|
||||
.replace("x-", "")
|
||||
|
||||
self.set_path(gfile)
|
||||
logger.debug(f"File content type: {self.ftype}")
|
||||
|
||||
def _load_data(self, text: str, is_new: bool = True):
|
||||
undo_manager = self.buffer.get_undo_manager()
|
||||
|
||||
self.buffer.block_changed_signal()
|
||||
@@ -151,21 +137,55 @@ class SourceFile(GtkSource.File):
|
||||
|
||||
self.buffer.delete(start_itr, end_itr)
|
||||
self.buffer.insert(start_itr, text, -1)
|
||||
self.is_externally_modified()
|
||||
GLib.idle_add(move_insert_to_start)
|
||||
|
||||
undo_manager.end_not_undoable_action()
|
||||
self.buffer.set_modified(False)
|
||||
|
||||
eve = Event_Factory.create_event(
|
||||
"loaded_new_file",
|
||||
file = self
|
||||
)
|
||||
self.emit(eve)
|
||||
if is_new:
|
||||
eve = Event_Factory.create_event(
|
||||
"loaded_new_file",
|
||||
file = self
|
||||
)
|
||||
self.emit(eve)
|
||||
|
||||
self.buffer.unblock_changed_signal()
|
||||
self.buffer.unblock_changed_after_signal()
|
||||
self.buffer.unblock_modified_changed_signal()
|
||||
|
||||
def is_externally_modified(self) -> bool:
|
||||
stat = os.stat(self.fpath)
|
||||
current = (stat.st_mtime_ns, stat.st_size)
|
||||
|
||||
is_modified = \
|
||||
hasattr(self, "last_state") and not current == self.last_state
|
||||
|
||||
self.last_state = current
|
||||
return is_modified
|
||||
|
||||
def load_path(self, gfile: Gio.File):
|
||||
if not gfile: return
|
||||
loaded, contents, etag_out = gfile.load_contents()
|
||||
if not loaded: raise Exception("File couldn't be loaded...'")
|
||||
|
||||
# Note:
|
||||
# "strict" (default) -> raises an error on invalid bytes
|
||||
# "ignore" -> skips invalid bytes entirely
|
||||
# "replace" -> replaces invalid bytes with <20>
|
||||
# "backslashreplace" -> uses escape sequences like \xFF
|
||||
text = contents.decode("UTF-8", errors = "replace")
|
||||
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) \
|
||||
.replace("application/", "") \
|
||||
.replace("text/", "") \
|
||||
.replace("x-", "")
|
||||
|
||||
del contents
|
||||
self.set_path(gfile)
|
||||
logger.debug(f"File content type: {self.ftype}")
|
||||
self._load_data(text)
|
||||
|
||||
def set_path(self, gfile: Gio.File):
|
||||
if not gfile: return
|
||||
@@ -177,9 +197,17 @@ class SourceFile(GtkSource.File):
|
||||
event = Event_Factory.create_event("file_path_set", file = self)
|
||||
self.emit(event)
|
||||
|
||||
def reload(self):
|
||||
loaded, contents, etag_out = self.get_location().load_contents()
|
||||
if not loaded: raise Exception("File couldn't be re-loaded...'")
|
||||
|
||||
text = contents.decode("UTF-8")
|
||||
self._load_data(text, False)
|
||||
|
||||
def save(self):
|
||||
self._write_file( self.get_location() )
|
||||
|
||||
self.is_externally_modified()
|
||||
self.buffer.set_modified(False)
|
||||
event = Event_Factory.create_event(
|
||||
"saved_file",
|
||||
|
||||
@@ -56,7 +56,7 @@ class ManifestManager:
|
||||
|
||||
if not manifest.autoload:
|
||||
self.manual_launch_manifests.append(manifest_meta)
|
||||
return
|
||||
return manifest_meta
|
||||
|
||||
if manifest.pre_launch:
|
||||
self.pre_launch_manifests.append(manifest_meta)
|
||||
|
||||
@@ -48,13 +48,13 @@ class PluginReloadMixin:
|
||||
|
||||
def remove_plugin(self, file: str) -> None:
|
||||
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()
|
||||
manifest_meta.instance = None
|
||||
self._plugin_collection.remove(manifest_meta)
|
||||
self.plugins_ui.remove_row(manifest_meta)
|
||||
manifests = self._manifest_manager.pre_launch_manifests \
|
||||
+ self._manifest_manager.post_launch_manifests \
|
||||
+ self._manifest_manager.manual_launch_manifests
|
||||
|
||||
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:
|
||||
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:
|
||||
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
|
||||
|
||||
@@ -76,6 +76,7 @@ class PluginsUI(Gtk.Dialog):
|
||||
|
||||
toggle_bttn.toggle_id = \
|
||||
toggle_bttn.connect("toggled", callback, manifest_meta)
|
||||
box.toggle_bttn = toggle_bttn
|
||||
|
||||
box.add(plugin_lbl)
|
||||
box.add(author_lbl)
|
||||
@@ -96,5 +97,5 @@ class PluginsUI(Gtk.Dialog):
|
||||
toggle_bttn.disconnect(toggle_bttn.toggle_id)
|
||||
|
||||
self.list_box.remove(row)
|
||||
box.destroy()
|
||||
child.destroy()
|
||||
break
|
||||
|
||||
@@ -63,14 +63,14 @@ scrollbar {
|
||||
|
||||
scrollbar trough {
|
||||
background-color: transparent;
|
||||
border-radius: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
scrollbar slider {
|
||||
background-color: rgba(255, 255, 255, 0.18);
|
||||
border-radius: 8px;
|
||||
min-width: 10px;
|
||||
min-height: 10px;
|
||||
border-radius: 4px;
|
||||
min-width: 5px;
|
||||
min-height: 5px;
|
||||
transition: 120ms ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user