diff --git a/plugins/code/event-watchers/file_state_watcher/plugin.py b/plugins/code/event-watchers/file_state_watcher/plugin.py index 5603f2f..49adb0b 100644 --- a/plugins/code/event-watchers/file_state_watcher/plugin.py +++ b/plugins/code/event-watchers/file_state_watcher/plugin.py @@ -17,13 +17,22 @@ class Plugin(PluginCode): def _controller_message(self, event: Code_Event_Types.CodeEvent): - if isinstance(event, Code_Event_Types.TextChangedEvent): - event.file.check_file_on_disk() + if not isinstance(event, Code_Event_Types.FocusedViewEvent): return + event = Event_Factory.create_event( + "get_file", buffer = event.view.get_buffer() + ) + self.emit_to("files", event) - if event.file.is_deleted(): - file_is_deleted(event, self.emit) - elif event.file.is_externally_modified(): - file_is_externally_modified(event, self.emit) + file = event.response + if not file: return + if file.ftype == "buffer": return + + file.check_file_on_disk() + + if file.is_deleted(): + file_is_deleted(file, self.emit) + elif file.is_externally_modified(): + file_is_externally_modified(file, self.emit) def load(self): ... diff --git a/plugins/code/event-watchers/file_state_watcher/watcher_checks.py b/plugins/code/event-watchers/file_state_watcher/watcher_checks.py index 51dfa78..f3d4f02 100644 --- a/plugins/code/event-watchers/file_state_watcher/watcher_checks.py +++ b/plugins/code/event-watchers/file_state_watcher/watcher_checks.py @@ -10,23 +10,45 @@ from libs.event_factory import Event_Factory, Code_Event_Types -def file_is_deleted(event, emit): - event.file.was_deleted = True +def ask_yes_no(message): + dialog = Gtk.MessageDialog( + parent = None, + flags = 0, + message_type = Gtk.MessageType.QUESTION, + buttons = Gtk.ButtonsType.YES_NO, + text = message, + ) + dialog.set_title("Confirm") + + response = dialog.run() + dialog.destroy() + + return response == Gtk.ResponseType.YES + + +def file_is_deleted(file, emit): + file.was_deleted = True event = Event_Factory.create_event( "file_externally_deleted", - file = event.file, - buffer = event.buffer + file = file, + buffer = file.buffer ) emit(event) -def file_is_externally_modified(event, emit): -# event = Event_Factory.create_event( -# "file_externally_modified", -# file = event.file, -# buffer = event.buffer -# ) -# emit(event) +def file_is_externally_modified(file, emit): + event = Event_Factory.create_event( + "file_externally_modified", + file = file, + buffer = file.buffer + ) + emit(event) - ... + if not file.buffer.get_modified(): + file.reload() + return + result = ask_yes_no("File has been externally modified. Reload?") + if not result: return + + file.reload() diff --git a/plugins/code/event-watchers/tree_sitter/languages/scripts/build.sh b/plugins/code/event-watchers/tree_sitter/languages/scripts/build.sh new file mode 100755 index 0000000..4e70a36 --- /dev/null +++ b/plugins/code/event-watchers/tree_sitter/languages/scripts/build.sh @@ -0,0 +1,134 @@ +#!/bin/bash + +# . CONFIG.sh + +# set -o xtrace ## To debug scripts +# set -o errexit ## To exit on error +# set -o errunset ## To exit if a variable is referenced but not set +#!/usr/bin/env bash +set -euo pipefail + + + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TOOLS="$ROOT/.tools" +NODE_DIR="$TOOLS/node" +GRAMMARS_DIR="$ROOT/grammars" +BUILD_DIR="$ROOT/build" +OUTPUT="$ROOT/compiled" +NODE_VERSION="v24.14.1" +NODE_DIST="node-$NODE_VERSION-linux-x64" +NODE_ARCHIVE="$TOOLS/node.tar.xz" +NODE_URL="https://nodejs.org/dist/$NODE_VERSION/$NODE_DIST.tar.xz" +TS_CLI_VERSION="0.22.6" + + +LANGS=( + tree-sitter-python + tree-sitter-javascript + tree-sitter-html + tree-sitter-css + tree-sitter-json + tree-sitter-java + tree-sitter-c + tree-sitter-cpp + tree-sitter-go +) + +REPOS=( + https://github.com/tree-sitter/tree-sitter-python + https://github.com/tree-sitter/tree-sitter-javascript + https://github.com/tree-sitter/tree-sitter-html + https://github.com/tree-sitter/tree-sitter-css + https://github.com/tree-sitter/tree-sitter-json + https://github.com/tree-sitter/tree-sitter-java + https://github.com/tree-sitter/tree-sitter-c + https://github.com/tree-sitter/tree-sitter-cpp + https://github.com/tree-sitter/tree-sitter-go +) + +mkdir -p "$TOOLS" "$GRAMMARS_DIR" "$BUILD_DIR" "$OUTPUT" + +ensure_node() { + if [ -x "$NODE_DIR/bin/node" ]; then + echo "==> Using cached Node.js" + return + fi + + echo "==> Downloading Node.js $NODE_VERSION" + wget -O "$NODE_ARCHIVE" "$NODE_URL" + + echo "==> Extracting Node.js" + mkdir -p "$NODE_DIR" + tar -xf "$NODE_ARCHIVE" -C "$NODE_DIR" --strip-components=1 + rm "$NODE_ARCHIVE" + + echo "==> Node installed at $NODE_DIR" +} + +ensure_tree_sitter() { + export PATH="$NODE_DIR/bin:$PATH" + TS="$TOOLS/node_modules/.bin/tree-sitter" + + if [ -x "$TS" ]; then + echo "==> Using cached tree-sitter-cli" + return + fi + + echo "==> Installing tree-sitter-cli" + cd "$TOOLS" + npm init -y >/dev/null 2>&1 || true + npm install tree-sitter-cli@$TS_CLI_VERSION +} + +sync_grammars() { + echo "==> Syncing grammars" + + for i in "${!LANGS[@]}"; do + NAME="${LANGS[$i]}" + REPO="${REPOS[$i]}" + TARGET="$GRAMMARS_DIR/$NAME" + + if [ -d "$TARGET/.git" ]; then + echo "Updating $NAME" + git -C "$TARGET" pull --depth 1 + else + echo "Cloning $NAME" + git clone --depth 1 "$REPO" "$TARGET" + fi + done +} + +build_lib() { + echo "==> Building Tree-sitter library" + + mkdir -p "$OUTPUT" + + PARSER_SRC=() + INCLUDE_PATHS=() + + for GRAMMAR in "${LANGS[@]/#/$GRAMMARS_DIR/}"; do + echo "==> Processing grammar $GRAMMAR" + + PARSER_SRC+=("$GRAMMAR/src/parser.c") + + if [[ -f "$GRAMMAR/src/scanner.c" ]]; then + PARSER_SRC+=("$GRAMMAR/src/scanner.c") + fi + + INCLUDE_PATHS+=("-I$GRAMMAR/src") + done + + gcc -shared -o "$OUTPUT/languages.so" "${PARSER_SRC[@]}" "${INCLUDE_PATHS[@]}" -fPIC + + echo "==> Output: $OUTPUT/languages.so" +} + +main() { + ensure_node + ensure_tree_sitter + sync_grammars + build_lib +} + +main "$@" diff --git a/plugins/code/event-watchers/tree_sitter/libs/tree_sitter_language_pack/__init__.py b/plugins/code/event-watchers/tree_sitter/libs/tree_sitter_language_pack/__init__.py deleted file mode 100644 index b26be0f..0000000 --- a/plugins/code/event-watchers/tree_sitter/libs/tree_sitter_language_pack/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -from typing import TypeAlias - -from tree_sitter_language_pack._native import ( - DownloadError, - LanguageNotFoundError, - ParseError, - ProcessConfig, - QueryError, - TreeHandle, - available_languages, - cache_dir, - clean_cache, - configure, - download, - download_all, - downloaded_languages, - get_binding, - get_language, - get_parser, - has_language, - init, - language_count, - manifest_languages, - parse_string, - process, -) - -SupportedLanguage: TypeAlias = str - -__all__ = [ - "DownloadError", - "LanguageNotFoundError", - "ParseError", - "ProcessConfig", - "QueryError", - "SupportedLanguage", - "TreeHandle", - "available_languages", - "cache_dir", - "clean_cache", - "configure", - "download", - "download_all", - "downloaded_languages", - "get_binding", - "get_language", - "get_parser", - "has_language", - "init", - "language_count", - "manifest_languages", - "parse_string", - "process", -] diff --git a/plugins/code/event-watchers/tree_sitter/libs/tree_sitter_language_pack/__init__.pyi b/plugins/code/event-watchers/tree_sitter/libs/tree_sitter_language_pack/__init__.pyi deleted file mode 100644 index bdc29d4..0000000 --- a/plugins/code/event-watchers/tree_sitter/libs/tree_sitter_language_pack/__init__.pyi +++ /dev/null @@ -1,276 +0,0 @@ -from typing import Literal, TypeAlias - -from tree_sitter import Language, Parser - -class LanguageNotFoundError(ValueError): ... -class DownloadError(RuntimeError): ... - -SupportedLanguage: TypeAlias = Literal[ - "actionscript", - "ada", - "agda", - "apex", - "arduino", - "asm", - "astro", - "bash", - "batch", - "bazel", - "beancount", - "bibtex", - "bicep", - "bitbake", - "bsl", - "c", - "cairo", - "capnp", - "chatito", - "clarity", - "clojure", - "cmake", - "cobol", - "comment", - "commonlisp", - "cpon", - "cpp", - "css", - "csv", - "cuda", - "d", - "dart", - "diff", - "dockerfile", - "doxygen", - "dtd", - "elisp", - "elixir", - "elm", - "erlang", - "fennel", - "firrtl", - "fish", - "fortran", - "fsharp", - "fsharp_signature", - "func", - "gdscript", - "gitattributes", - "gitcommit", - "gitignore", - "gleam", - "glsl", - "gn", - "go", - "gomod", - "gosum", - "gradle", - "graphql", - "groovy", - "gstlaunch", - "hack", - "hare", - "haskell", - "haxe", - "hcl", - "heex", - "hlsl", - "html", - "hyprlang", - "ignorefile", - "ini", - "ispc", - "janet", - "java", - "javascript", - "jsdoc", - "json", - "jsonnet", - "julia", - "kconfig", - "kdl", - "kotlin", - "latex", - "linkerscript", - "lisp", - "llvm", - "lua", - "luadoc", - "luap", - "luau", - "magik", - "make", - "makefile", - "markdown", - "markdown_inline", - "matlab", - "mermaid", - "meson", - "netlinx", - "nim", - "ninja", - "nix", - "nqc", - "objc", - "ocaml", - "ocaml_interface", - "odin", - "org", - "pascal", - "pem", - "perl", - "pgn", - "php", - "pkl", - "po", - "pony", - "powershell", - "printf", - "prisma", - "properties", - "proto", - "psv", - "puppet", - "purescript", - "pymanifest", - "python", - "qmldir", - "qmljs", - "query", - "r", - "racket", - "re2c", - "readline", - "rego", - "requirements", - "ron", - "rst", - "ruby", - "rust", - "scala", - "scheme", - "scss", - "shell", - "smali", - "smithy", - "solidity", - "sparql", - "sql", - "squirrel", - "starlark", - "svelte", - "swift", - "tablegen", - "tcl", - "terraform", - "test", - "thrift", - "toml", - "tsv", - "tsx", - "twig", - "typescript", - "typst", - "udev", - "ungrammar", - "uxntal", - "v", - "verilog", - "vhdl", - "vim", - "vue", - "wast", - "wat", - "wgsl", - "xcompose", - "xml", - "yuck", - "zig", -] - -class ParseError(RuntimeError): ... -class QueryError(ValueError): ... - -class ProcessConfig: - language: str - structure: bool - imports: bool - exports: bool - comments: bool - docstrings: bool - symbols: bool - diagnostics: bool - chunk_max_size: int | None - - def __init__( - self, - language: str, - *, - structure: bool = True, - imports: bool = True, - exports: bool = True, - comments: bool = True, - docstrings: bool = True, - symbols: bool = True, - diagnostics: bool = True, - chunk_max_size: int | None = None, - ) -> None: ... - @staticmethod - def all(language: str) -> ProcessConfig: ... - @staticmethod - def minimal(language: str) -> ProcessConfig: ... - -class TreeHandle: - def root_node_type(self) -> str: ... - def root_child_count(self) -> int: ... - def contains_node_type(self, node_type: str) -> bool: ... - def has_error_nodes(self) -> bool: ... - def to_sexp(self) -> str: ... - def error_count(self) -> int: ... - def root_node_info(self) -> dict[str, object]: ... - def find_nodes_by_type(self, node_type: str) -> list[dict[str, object]]: ... - def named_children_info(self) -> list[dict[str, object]]: ... - def extract_text(self, start_byte: int, end_byte: int) -> str: ... - def run_query(self, language: str, query_source: str) -> list[dict[str, object]]: ... - -__all__ = [ - "DownloadError", - "LanguageNotFoundError", - "ParseError", - "ProcessConfig", - "QueryError", - "SupportedLanguage", - "TreeHandle", - "available_languages", - "cache_dir", - "clean_cache", - "configure", - "download", - "download_all", - "downloaded_languages", - "get_binding", - "get_language", - "get_parser", - "has_language", - "init", - "language_count", - "manifest_languages", - "parse_string", - "process", -] - -def get_binding(name: SupportedLanguage) -> object: ... -def get_language(name: SupportedLanguage) -> Language: ... -def get_parser(name: SupportedLanguage) -> Parser: ... -def available_languages() -> list[str]: ... -def has_language(name: str) -> bool: ... -def language_count() -> int: ... -def parse_string(language: str, source: str) -> TreeHandle: ... -def process(source: str, config: ProcessConfig) -> dict[str, object]: ... -def init(config: dict[str, object]) -> None: ... -def configure(*, cache_dir: str | None = None) -> None: ... -def download(names: list[str]) -> int: ... -def download_all() -> int: ... -def manifest_languages() -> list[str]: ... -def downloaded_languages() -> list[str]: ... -def clean_cache() -> None: ... -def cache_dir() -> str: ... diff --git a/plugins/code/event-watchers/tree_sitter/libs/tree_sitter_language_pack/py.typed b/plugins/code/event-watchers/tree_sitter/libs/tree_sitter_language_pack/py.typed deleted file mode 100644 index e69de29..0000000 diff --git a/plugins/code/event-watchers/tree_sitter/manifest.json b/plugins/code/event-watchers/tree_sitter/manifest.json index fa63b0e..6d938f5 100644 --- a/plugins/code/event-watchers/tree_sitter/manifest.json +++ b/plugins/code/event-watchers/tree_sitter/manifest.json @@ -4,6 +4,5 @@ "version": "0.0.1", "support": "", "pre_launch": true, - "autoload": false, "requests": {} } diff --git a/plugins/code/event-watchers/tree_sitter/notes/compile.py b/plugins/code/event-watchers/tree_sitter/notes/compile.py deleted file mode 100644 index 0e28feb..0000000 --- a/plugins/code/event-watchers/tree_sitter/notes/compile.py +++ /dev/null @@ -1,17 +0,0 @@ -from .tree_sitter import Language - -Language.build_library( - "my-languages.so", - [ - "tree-sitter-python", - "tree-sitter-javascript", - "tree-sitter-html", - "tree-sitter-css", - "tree-sitter-json", - "tree-sitter-java", - "tree-sitter-c", - "tree-sitter-cpp", - "tree-sitter-go", - "tree-sitter-gdscript", - ], -) diff --git a/plugins/code/event-watchers/tree_sitter/notes/setup-tree-sitter.sh b/plugins/code/event-watchers/tree_sitter/notes/setup-tree-sitter.sh deleted file mode 100755 index 0082597..0000000 --- a/plugins/code/event-watchers/tree_sitter/notes/setup-tree-sitter.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/bash - -# . CONFIG.sh - -# set -o xtrace ## To debug scripts -# set -o errexit ## To exit on error -# set -o errunset ## To exit if a variable is referenced but not set - - - - -function old_build() { - touch __init__.py - -cat <<'EOF' > compile.py -from tree_sitter import Language - -Language.build_library( - "my-languages.so", - [ - "tree-sitter-python", - "tree-sitter-javascript", - "tree-sitter-html", - "tree-sitter-css", - "tree-sitter-json", - "tree-sitter-java", - "tree-sitter-c", - "tree-sitter-cpp", - "tree-sitter-go" - "tree-sitter-gdscript", - ], -) -EOF - - python compile.py - cd .. -} - -function build() { - touch __init__.py - -cat <<'EOF' > compile.py -from tree_sitter_language_pack import init, download, get_language, get_parser, available_languages -from tree_sitter_language_pack import process, ProcessConfig - -# Optional: Pre-download specific languages for offline use -# init(["python", "javascript", "rust"]) - -# Get a language (auto-downloads if not cached) -language = get_language("python") - -# Get a pre-configured parser (auto-downloads if needed) -parser = get_parser("python") -tree = parser.parse(b"def hello(): pass") -print(tree.root_node) - -# List all available languages -for lang in available_languages(): - print(lang) - - -# Extract file intelligence (auto-downloads language if needed) -result = process( - "def hello(): pass", - ProcessConfig( - language = "python" - ) -) -print(f"Functions: {len(result['structure'])}") - -# Pre-download languages for offline use -download(["python", "javascript"]) - -# With chunking -result = process( - source, - ProcessConfig( - language = "python", - chunk_max_size = 1000, - comments = True - ) -) -print(f"Chunks: {len(result['chunks'])}") - -EOF - - python compile.py - cd .. -} - -function clone() { - git clone --depth 1 https://github.com/tree-sitter/tree-sitter-python - git clone --depth 1 https://github.com/tree-sitter/tree-sitter-javascript - git clone --depth 1 https://github.com/tree-sitter/tree-sitter-html - git clone --depth 1 https://github.com/tree-sitter/tree-sitter-css - git clone --depth 1 https://github.com/tree-sitter/tree-sitter-json - git clone --depth 1 https://github.com/tree-sitter/tree-sitter-java - git clone --depth 1 https://github.com/tree-sitter/tree-sitter-c - git clone --depth 1 https://github.com/tree-sitter/tree-sitter-cpp - git clone --depth 1 https://github.com/tree-sitter/tree-sitter-go -# git clone --depth 1 https://github.com/godotengine/tree-sitter-gdscript -} - -function setup() { -# pip install tree-sitter -t . - pip install tree-sitter-language-pack -t . -# pip install tree_sitter_languages -t . # Old unmaintained library -} - -function main() { - cd "$(dirname "$0")" - echo "Working Dir: " $(pwd) - - mkdir -p build - cd build - -# clone "$@" - setup "$@" - build "$@" -} -main "$@"; diff --git a/plugins/code/event-watchers/tree_sitter/plugin.py b/plugins/code/event-watchers/tree_sitter/plugin.py index 4edbc0f..702c01d 100644 --- a/plugins/code/event-watchers/tree_sitter/plugin.py +++ b/plugins/code/event-watchers/tree_sitter/plugin.py @@ -1,14 +1,4 @@ # 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 @@ -17,7 +7,7 @@ from libs.event_factory import Event_Factory, Code_Event_Types from plugins.plugin_types import PluginCode -import tree_sitter_language_pack as tslp +from .tree_sitter import Parser, get_parser @@ -28,50 +18,30 @@ class Plugin(PluginCode): 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 + if not hasattr(event.file, "tree_sitter"): + parser = get_parser( event.file.ftype ) + if not parser: return - buffer = event.file.buffer + event.file.tree_sitter = parser + + buffer = event.file.buffer start_itr, \ end_itr = buffer.get_bounds() text = buffer.get_text(start_itr, end_itr, True) - result = tslp.process( - text, - tslp.ProcessConfig( - language = event.file.ftype - ) - ) - event.file.tree_sitter_meta = result + tree = event.file.tree_sitter.parse( text.encode("UTF-8") ) + event.file.ast = tree + +# root = tree.root_node +# print("Root type:", root.type) +# for child in root.children: +# print(child.type, child.start_point, child.end_point) def load(self): - 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" - ... diff --git a/plugins/code/event-watchers/tree_sitter/tree_sitter.py b/plugins/code/event-watchers/tree_sitter/tree_sitter.py new file mode 100644 index 0000000..2903a77 --- /dev/null +++ b/plugins/code/event-watchers/tree_sitter/tree_sitter.py @@ -0,0 +1,57 @@ +# Python imports +import os +import ctypes + +# Lib imports + +# Application imports +from .libs.tree_sitter import Language, Parser + + + +_LIB_PATH = os.path.join( + os.path.dirname(__file__), "languages", "languages.so" +) +_LANGUAGE_LIB = ctypes.CDLL(_LIB_PATH) + + + +def load_language(name: str) -> Language | None: + symbol = f"tree_sitter_{name}" + + try: + func = getattr(_LANGUAGE_LIB, symbol) + except AttributeError: + logger.warning(f"Tree-sitter: {name} not found in 'languages.so' shared library...") + return None + + func.restype = ctypes.c_void_p + return Language(func()) + + +LANGUAGES = { + "python": load_language("python"), + "python3": load_language("python"), + "javascript": load_language("javascript"), + "html": load_language("html"), + "css": load_language("css"), + "json": load_language("json"), + "java": load_language("java"), + "c": load_language("c"), + "cpp": load_language("cpp"), + "go": load_language("go") +} + + +def get_parser(lang_name: str) -> Parser | None: + if not lang_name in LANGUAGES: return + + language = LANGUAGES[lang_name] + + if not language in LANGUAGES: return + + parser = Parser() + parser.language = language + + return parser + diff --git a/src/core/controllers/base_controller_mixin.py b/src/core/controllers/base_controller_mixin.py index c0c9eee..6862b46 100644 --- a/src/core/controllers/base_controller_mixin.py +++ b/src/core/controllers/base_controller_mixin.py @@ -18,16 +18,12 @@ class BaseControllerMixin: files = [] for arg in unknownargs + [args.new_tab,]: - if os.path.isdir( arg.replace("file://", "") ): - files.append( f"DIR|{arg.replace('file://', '')}" ) - continue + if os.path.isfile(arg): + files.append(f"{arg}") - # NOTE: If passing line number with file split against : - if os.path.isfile( arg.replace("file://", "").split(":")[0] ): - files.append( f"FILE|{arg.replace('file://', '')}" ) - continue - - logger.info(f"Not a File: {arg}") + if os.path.isdir(arg): + message = f"DIR|{arg}" + ipc_server.send_ipc_message(message) if not files: return