10 Commits

Author SHA1 Message Date
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
62731ae766 Improve file loading robustness and drag-and-drop handling
- Add error handling for UTF-8 decoding to gracefully handle invalid bytes
- Refactor DnD target handling with explicit target list and proper context completion
- Remove completed TODO item for file open event emission
- Clean up memory by deleting file contents after decoding
2026-03-23 01:32:57 -05:00
b5cec0d049 Fix multi-select word movement for snake_case and add Ctrl+scroll zoom
- Implement snake_case-aware word movement that treats underscores as part of words
- Fix selection edge detection to only move when caret is at appropriate boundary
- Add INSERT state guards to line_up/down commands
- Add Ctrl+scroll zoom in/out functionality for text size
- Remove obsolete newton.zip archive
2026-03-22 23:42:28 -05:00
c821f30880 Added TODOs; made explicit that LSP Manager pre launch if autoload true/unset; added WIP tree_sitter plugin 2026-03-22 17:56:48 -05:00
13908d7ba7 Refactor file loading to detect external modifications and add reload capability
- Extract _load_data() method from load_path() for reuse in reload()
- Add is_extters externally_modified() to track file state changes (mtime/size)
- Replace load_bytes() with load_contents() for better error handling
- Add reload() method to re-read file from disk
- Fix file_state_watcher by properly detecting external changes
- Update TODO list (add Terminal plugin, mark file_state_watcher as fixed)
2026-03-22 00:35:11 -05:00
ec69d4db73 Addressed 'tabs_bar' TODO 2026-03-21 18:16:20 -05:00
511138316a Addressed Telescope TODO; enhanced Telescope search 2026-03-21 17:19:10 -05:00
d6e0823e21 Adding images to README file 2026-03-21 15:29:12 -05:00
54e7b58c24 Updated README 2026-03-21 14:16:23 -05:00
46 changed files with 1437 additions and 120 deletions

View File

@@ -1,25 +1,13 @@
# Python-With-Gtk-Template # Newton
A template project for Python with Gtk applications. A Python + Gtk 3 based quasi-IDE.
### 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)
### Note ### Note
* pyrightconfig.json can prompt IDEs that use pyright lsp on where imports are located- look at venvPath and venv. "venvPath" is parent path of "venv" where "venv" is just the name of the folder under the parent path that is the python created venv. [TODO](TODO.md)
* 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
### Images
For the user_config, after changing names and files, copy all content to their respective destinations. ![1 Newton default view.](images/pic1.png)
The logic follows Debian Dpkg packaging and its placement logic. ![2 Newton split pane view.](images/pic2.png)
![3 Newton search and replace shown.](images/pic3.png)
![4 Newton displaying inline colors.](images/pic4.png)
![5 Newton as transparent with youtube playing below it.](images/pic5.png)
![6 Newton with plugins menu show as well as markdown preview shown.](images/pic6.png)

19
TODO.md
View File

@@ -1,22 +1,19 @@
___ ___
### Add ### Add
1. Add Godot LSP Client 1. Add TreeSitter
1. Add Collapsable code blocks 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 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
___ ___
### Change ### Change
1. Make **telescope** plugin a generic base to allow query mode additions through plugins 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 1. Make **lsp_manager** hard coded values configurable, plus add respective fields to UI
___
### Fix ___
- Fix **telescope** search selection when items hidden such that ### Fix
hidden items don't get selected on up/down key - Fix on lsp client unload to close files lsp side and unload server endpoint
- 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
___ ___

BIN
images/pic1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
images/pic2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
images/pic3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
images/pic4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
images/pic5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 951 KiB

BIN
images/pic6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 901 KiB

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

@@ -13,8 +13,6 @@ from gi.repository import Gtk
def add_prettify_json(buffer, menu): def add_prettify_json(buffer, menu):
menu.append(separator)
def on_prettify_json(menuitem, buffer): def on_prettify_json(menuitem, buffer):
start_itr, \ start_itr, \
end_itr = buffer.get_start_iter(), buffer.get_end_iter() end_itr = buffer.get_start_iter(), buffer.get_end_iter()

View File

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

View File

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

View 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'])}")

View File

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

View File

@@ -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",
]

View File

@@ -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]

View File

@@ -0,0 +1,54 @@
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

@@ -0,0 +1,276 @@
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

@@ -0,0 +1,9 @@
{
"name": "Tree-sitter",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"pre_launch": true,
"autoload": false,
"requests": {}
}

View File

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,121 @@
#!/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

@@ -0,0 +1,77 @@
# 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
# Application imports
from libs.event_factory import Event_Factory, Code_Event_Types
from plugins.plugin_types import PluginCode
import tree_sitter_language_pack as tslp
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 tslp.has_language( event.file.ftype ):
try:
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
start_itr, \
end_itr = buffer.get_bounds()
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
def load(self):
tslp.configure(
cache_dir = f"{BASE_DIR}/cache/tree-sitter-language-pack/v1.0.0/libs"
)
def unload(self):
...
def run(self):
# tslp.download(
# [
# "python",
# "java",
# "go",
# "c",
# "cpp",
# "javascript",
# "html",
# "css",
# "json"
# ]
# )
# "gdscript"
...

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

@@ -3,6 +3,7 @@
"author": "ITDominator", "author": "ITDominator",
"version": "0.0.1", "version": "0.0.1",
"support": "", "support": "",
"pre_launch": true,
"autoload": false, "autoload": false,
"requests": {} "requests": {}
} }

View File

@@ -1,6 +1,9 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
from gi.repository import Gtk
# Application imports # Application imports
from libs.event_factory import Event_Factory, Code_Event_Types from libs.event_factory import Event_Factory, Code_Event_Types
@@ -25,8 +28,19 @@ class Plugin(PluginCode):
self.register_controller("tabs", self.tabs_controller) self.register_controller("tabs", self.tabs_controller)
code_container.add( self.tabs_controller.tabs_widget ) scrolled_win = Gtk.ScrolledWindow()
code_container.reorder_child(self.tabs_controller.tabs_widget, 0) 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") event = Event_Factory.create_event("get_files")
self.emit_to("files", event) self.emit_to("files", event)
@@ -36,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

@@ -27,7 +27,7 @@ class TabsWidget(Gtk.Notebook):
def _setup_styling(self): def _setup_styling(self):
self.set_scrollable(True) ...
def _setup_signals(self): def _setup_signals(self):
self.connect("page-added", self._page_added) self.connect("page-added", self._page_added)
@@ -67,6 +67,7 @@ class TabsWidget(Gtk.Notebook):
) )
self.emit(event) self.emit(event)
self._scroll_to_center(tab)
def _bind_tab_menu(self, tab, page_widget): def _bind_tab_menu(self, tab, page_widget):
def do_context_menu(tab, eve, page_widget): def do_context_menu(tab, eve, page_widget):
@@ -81,6 +82,21 @@ class TabsWidget(Gtk.Notebook):
page_widget 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: def create_menu(self, page_widget) -> Gtk.Menu:
context_menu = Gtk.Menu() context_menu = Gtk.Menu()
close_submenu = Gtk.Menu() close_submenu = Gtk.Menu()
@@ -130,6 +146,7 @@ class TabsWidget(Gtk.Notebook):
self.page_num(page_widget) self.page_num(page_widget)
) )
self.handler_unblock(self.switch_page_id) self.handler_unblock(self.switch_page_id)
self._scroll_to_center(tab)
break break

View File

@@ -45,6 +45,7 @@ class ListBox(Gtk.ListBox):
def _row_activated(self, list_box, row = None): def _row_activated(self, list_box, row = None):
row = self.get_selected_row() row = self.get_selected_row()
if not row: return
file = row.get_children()[0].file file = row.get_children()[0].file
event = Event_Factory.create_event( event = Event_Factory.create_event(
@@ -70,24 +71,62 @@ class ListBox(Gtk.ListBox):
def search_changed(self, entry): def search_changed(self, entry):
self.search_buffer_names(entry) self.search_buffer_names(entry)
for row in self.get_children(): for row in self.get_children():
if not row.is_visible(): continue if not row.is_visible(): continue
self.select_row(row) 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): def search_buffer_names(self, entry):
text = entry.get_text() query = entry.get_text().lower()
if not text: rows = []
for row in self.get_children():
row.show()
return
for row in self.get_children(): 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() \ rows.append((score, row, child, label_text))
if text in child.get_label() else \
row.hide() 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): def activate_row(self):
self._row_activated(self) self._row_activated(self)
@@ -96,30 +135,44 @@ class ListBox(Gtk.ListBox):
raise TelescopeListBoxException("ListBox must have 'set_buffer' monkey patched...") raise TelescopeListBoxException("ListBox must have 'set_buffer' monkey patched...")
def move_row_selection_up(self): def move_row_selection_up(self):
row = self.get_selected_row() row = self.get_selected_row()
next_row = self.get_row_at_index(row.get_index() - 1) if not row: return
if not next_row: rows = [r for r in self.get_children() if r.is_visible()]
next_row = self.get_row_at_index( if not rows: return
len( self.get_children() ) - 1
)
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): def move_row_selection_down(self):
row = self.get_selected_row() row = self.get_selected_row()
next_row = self.get_row_at_index(row.get_index() + 1) if not row: return
if not next_row: rows = [r for r in self.get_children() if r.is_visible()]
next_row = self.get_row_at_index(0) 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): def add_row(self, file):
label = Gtk.Label(label = file.fname) row = Gtk.ListBoxRow()
label = Gtk.Label(label = file.fname)
label.file = file label.file = file
label.show()
self.add(label) row.add(label)
row.show_all()
self.add(row)
def remove_row(self, event): def remove_row(self, event):
for row in self.get_children(): for row in self.get_children():

View File

@@ -8,6 +8,7 @@ gi.require_version('GtkSource', '4')
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
from libs.dto.states import SourceViewStates
@@ -17,4 +18,6 @@ def execute(
**kwargs **kwargs
): ):
logger.debug("Command: Line Down") logger.debug("Command: Line Down")
if not view.state == SourceViewStates.INSERT: return
view.emit("move-lines", True) view.emit("move-lines", True)

View File

@@ -8,6 +8,7 @@ gi.require_version('GtkSource', '4')
from gi.repository import GtkSource from gi.repository import GtkSource
# Application imports # Application imports
from libs.dto.states import SourceViewStates
@@ -17,4 +18,6 @@ def execute(
**kwargs **kwargs
): ):
logger.debug("Command: Line Up") logger.debug("Command: Line Up")
if not view.state == SourceViewStates.INSERT: return
view.emit("move-lines", False) view.emit("move-lines", False)

View File

@@ -56,10 +56,28 @@ class MarkerManager(MarkSupportMixin):
continue continue
if has_selection: 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) 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 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) self._move_iter(buffer, end_itr, mode, is_forward)
buffer.move_mark(start_mark, end_itr) buffer.move_mark(start_mark, end_itr)
@@ -81,17 +99,50 @@ class MarkerManager(MarkSupportMixin):
left = end_itr left = end_itr
right = start_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 collapse_itr = right if is_forward else left
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 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): 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":
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": elif mode == "line":
line = itr_.get_line() line = itr_.get_line()
offset = itr_.get_line_offset() offset = itr_.get_line_offset()

View File

@@ -57,6 +57,7 @@ class SourceViewSignalMapper:
"key-release-event": self._key_release_event, "key-release-event": self._key_release_event,
"button-press-event": self._button_press_event, "button-press-event": self._button_press_event,
"button-release-event": self._button_release_event, "button-release-event": self._button_release_event,
"scroll-event": self._scroll_event,
"populate-popup": self._populate_popup "populate-popup": self._populate_popup
} }
@@ -81,5 +82,8 @@ class SourceViewSignalMapper:
def _button_release_event(self, source_view: SourceView, eve): def _button_release_event(self, source_view: SourceView, eve):
return self.state_manager.handle_button_release_event(source_view, 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) return self.state_manager.handle_populate_popup(source_view, menu, self.emit)

View File

@@ -53,8 +53,15 @@ class SourceViewStateManager:
def handle_button_release_event(self, source_view, eve): def handle_button_release_event(self, source_view, eve):
return self.states[source_view.state].button_release_event(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): 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): def _handle_multi_insert_toggle(self, source_view, eve):
is_control = self.key_mapper.is_control(eve) is_control = self.key_mapper.is_control(eve)

View File

@@ -1,6 +1,9 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
gi.require_version('Gdk', '3.0')
from gi.repository import Gdk
# Application imports # Application imports
from libs.event_factory import Event_Factory, Code_Event_Types from libs.event_factory import Event_Factory, Code_Event_Types
@@ -79,6 +82,29 @@ class SourceViewsBaseState:
return True if not response else response 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): def populate_popup(self, source_view, menu, emit):
buffer = source_view.get_buffer() buffer = source_view.get_buffer()
event = Event_Factory.create_event( event = Event_Factory.create_event(

View File

@@ -61,7 +61,6 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
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):
is_forward = count > 0 is_forward = count > 0
buffer = source_view.get_buffer() buffer = source_view.get_buffer()
@@ -78,6 +77,8 @@ class SourceViewsMultiInsertState(SourceViewsBaseState):
self._signal_cursor_moved(source_view, emit) self._signal_cursor_moved(source_view, emit)
return False
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)

View File

@@ -106,7 +106,6 @@ class MarkSupportMixin:
name = f"multi-insert-end-{hash}", name = f"multi-insert-end-{hash}",
left_gravity = False left_gravity = False
) )
# left_gravity = True
buffer.add_mark(start_mark, target_iter) buffer.add_mark(start_mark, target_iter)
buffer.add_mark(end_mark, target_iter) buffer.add_mark(end_mark, target_iter)

View File

@@ -3,7 +3,10 @@
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
#gi.require_version('Gdk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
#from gi.repository import Gdk
# Application imports # Application imports
@@ -12,25 +15,41 @@ from gi.repository import Gtk
class SourceViewDnDMixin: class SourceViewDnDMixin:
def _set_up_dnd(self): def _set_up_dnd(self):
PLAIN_TEXT_TARGET_TYPE = 70 URI_TARGET_TYPE = 10
URI_TARGET_TYPE = 80 PLAIN_TEXT_TARGET_TYPE = 50
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) uri_target = Gtk.TargetEntry.new(
targets = [ text_target, uri_target ] '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) self.drag_dest_set_target_list(targets)
def _on_drag_data_received(self, widget, drag_context, x, y, data, info, time): def _on_drag_data_received(
if info == 70: return 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() uris = data.get_uris()
if not uris: if not uris:
uris = data.get_text().split("\n") uris = data.get_text().split("\n")
self._on_uri_data_received(uris) 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]): def _on_uri_data_received(self, uris: list[str]):
uris = self.command.filter_out_loaded_files(uris) uris = self.command.filter_out_loaded_files(uris)
if not uris: return if not uris: return

View File

@@ -120,21 +120,7 @@ class SourceFile(GtkSource.File):
return gfile return gfile
def _load_data(self, text: str, is_new: bool = True):
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}")
undo_manager = self.buffer.get_undo_manager() undo_manager = self.buffer.get_undo_manager()
self.buffer.block_changed_signal() self.buffer.block_changed_signal()
@@ -151,21 +137,55 @@ class SourceFile(GtkSource.File):
self.buffer.delete(start_itr, end_itr) self.buffer.delete(start_itr, end_itr)
self.buffer.insert(start_itr, text, -1) self.buffer.insert(start_itr, text, -1)
self.is_externally_modified()
GLib.idle_add(move_insert_to_start) GLib.idle_add(move_insert_to_start)
undo_manager.end_not_undoable_action() undo_manager.end_not_undoable_action()
self.buffer.set_modified(False) self.buffer.set_modified(False)
eve = Event_Factory.create_event( if is_new:
"loaded_new_file", eve = Event_Factory.create_event(
file = self "loaded_new_file",
) file = self
self.emit(eve) )
self.emit(eve)
self.buffer.unblock_changed_signal() self.buffer.unblock_changed_signal()
self.buffer.unblock_changed_after_signal() self.buffer.unblock_changed_after_signal()
self.buffer.unblock_modified_changed_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): def set_path(self, gfile: Gio.File):
if not gfile: return if not gfile: return
@@ -177,9 +197,17 @@ class SourceFile(GtkSource.File):
event = Event_Factory.create_event("file_path_set", file = self) event = Event_Factory.create_event("file_path_set", file = self)
self.emit(event) 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): def save(self):
self._write_file( self.get_location() ) self._write_file( self.get_location() )
self.is_externally_modified()
self.buffer.set_modified(False) self.buffer.set_modified(False)
event = Event_Factory.create_event( event = Event_Factory.create_event(
"saved_file", "saved_file",

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

@@ -63,14 +63,14 @@ scrollbar {
scrollbar trough { scrollbar trough {
background-color: transparent; background-color: transparent;
border-radius: 8px; border-radius: 4px;
} }
scrollbar slider { scrollbar slider {
background-color: rgba(255, 255, 255, 0.18); background-color: rgba(255, 255, 255, 0.18);
border-radius: 8px; border-radius: 4px;
min-width: 10px; min-width: 5px;
min-height: 10px; min-height: 5px;
transition: 120ms ease-in-out; transition: 120ms ease-in-out;
} }