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
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
![1 Newton default view.](images/pic1.png)
![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
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

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:
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()}"
]
)

View File

@@ -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):

View File

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

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 .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)

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."""
...

View File

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

View File

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

View File

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

View File

@@ -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():

View File

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

View File

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

View File

@@ -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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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)

View File

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

View File

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

View File

@@ -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;
}