Compare commits

...
37 Commits
Author SHA1 Message Date
Christoph 7c834176a3 Merge pull request 'fix wired namspace call in poco' (#22) from tcl_language_support into main
/ build_and_publish (push) Successful in 32s
Reviewed-on: #22
2025-08-04 12:51:43 +00:00
Christoph Brandau 72be83c282 fix wired namspace call in poco 2025-08-04 14:51:07 +02:00
Christoph Brandau fa5ea00384 add test cases 2025-08-04 14:11:55 +02:00
Christoph a8b4fd9d1e Update .gitea/workflows/build_and_puplish.yml 2025-08-04 11:42:53 +00:00
Christoph 35d0f20fba Update version to 0.4.13 2025-08-04 11:33:48 +00:00
Christoph 440ebc4df5 Update package.json
/ build_and_publish (push) Successful in 26s
2025-08-04 11:31:39 +00:00
Christoph 1b48cc1983 Update .gitea/workflows/build_and_puplish.yml
/ build_and_publish (push) Failing after 23s
2025-08-04 11:30:13 +00:00
Christoph 6cde05b786 Update version to 0.4.13 2025-08-04 10:59:08 +00:00
Christoph cde58db3d7 Merge pull request 'tcl_language_support' (#21) from tcl_language_support into main
/ build_and_publish (push) Successful in 29s
Reviewed-on: #21
2025-08-04 10:58:10 +00:00
Christoph Brandau 3613391c23 fix semantics with var in array 2025-08-04 12:57:01 +02:00
Christoph Brandau 894a5b8b52 add more test cases 2025-08-04 12:48:21 +02:00
Christoph Brandau c644ac9bd3 add changes from tclint 2025-08-04 12:48:02 +02:00
Christoph Brandau fd5b1f2e71 add poco replace plugin 2025-08-04 12:47:49 +02:00
Christoph Brandau 57c65221d3 add LIB_GE_command_buffer_edit_replace to plugins 2025-08-04 09:17:30 +02:00
Christoph 528c7a6b9e add more ignore files 2025-08-03 21:54:45 +02:00
Christoph 2d034838b4 Update package.json 2025-08-03 19:45:39 +00:00
Christoph f0ed8ac708 Update version to 0.5.0 2025-08-03 19:44:23 +00:00
Christoph 6a30c1ab53 Update .gitea/workflows/build_and_puplish.yml
/ build_and_publish (push) Successful in 37s
2025-08-03 19:43:20 +00:00
Christoph 6d404f26f2 Update .gitea/workflows/build_and_puplish.yml
/ build_and_publish (push) Failing after 27s
2025-08-03 19:39:13 +00:00
Christoph afc675f47f Update .gitea/workflows/build_and_puplish.yml
/ build_and_publish (push) Failing after 22s
2025-08-03 19:37:43 +00:00
Christoph d2936ec5f6 Update .gitea/workflows/build_and_puplish.yml
/ build_and_publish (push) Failing after 37s
2025-08-03 19:36:28 +00:00
Christoph 8b44b41d68 Update .gitea/workflows/build_and_puplish.yml 2025-08-03 19:35:31 +00:00
Christoph 5124c12bc6 Update version to 0.4.12 2025-08-03 19:30:13 +00:00
Christoph 32061809a7 Merge pull request 'test new pipline' (#20) from tcl_language_support into main
/ build_and_publish (push) Successful in 47s
Reviewed-on: #20
2025-08-03 19:27:59 +00:00
Christoph f762c42e00 test new pipline 2025-08-03 21:27:09 +02:00
Christoph 4ccdf9e9f3 Update version to 0.4.11 2025-08-03 19:09:05 +00:00
Christoph b86d4f7b58 Merge pull request 'tcl_language_support' (#19) from tcl_language_support into main
/ build_and_publish (push) Successful in 30s
Reviewed-on: #19
2025-08-03 19:07:19 +00:00
Christoph 0318532308 add some features 2025-08-03 21:04:14 +02:00
Christoph Brandau 5e2e85830b update 2025-08-01 13:35:02 +02:00
Christoph Brandau 98e2d104a8 update lsp_server 2025-07-31 16:19:09 +02:00
Christoph Brandau 44fd772151 Merge branch 'tcl_language_support' of https://git.cbsk-tech.de/Christoph/nx_post_support into tcl_language_support 2025-07-31 09:38:10 +02:00
Christoph Brandau d90f9b5425 adjsut completion Items 2025-07-31 09:38:02 +02:00
Christoph fed8b84b46 add more semantic tokens 2025-07-30 22:10:27 +02:00
Christoph Brandau ed045aa459 update completion items 2025-07-30 17:28:18 +02:00
Christoph Brandau 8da61abfce Merge branch 'main' into tcl_language_support 2025-07-28 22:23:08 +02:00
Christoph Brandau 0ea2cbec87 add noxfile.py 2025-07-28 22:21:49 +02:00
Christoph d99d7c6bf6 Update version to 0.4.10 2025-07-28 20:19:47 +00:00
16 changed files with 645 additions and 248 deletions
+10 -1
View File
@@ -1,7 +1,7 @@
on:
push:
tags:
- "*"
- "*"
jobs:
build_and_publish:
runs-on: ubuntu-latest
@@ -39,3 +39,12 @@ jobs:
run: vsce publish
env:
VSCE_PAT: ${{ secrets.VSCODE_MARKETPALCE }}
- name: Pack Extension for Release
run: vsce pack
- name: Upload VSIX to Release
uses: softprops/action-gh-release@v1
with:
files: "nx-post-support-*.vsix"
# generate_release_notes: true # Automatische Release Notes
# draft: false # N
# prerelease: false
+5 -1
View File
@@ -11,7 +11,11 @@ esbuild.js
.gitea
.venv/**
.nox/**
server/.venv/**
server/.nox/**
**/__pycache__/**
**/requirements.txt
**/requirements.in
**/server/src/_debug_server.py
**/server/src/_debug_server.py
server/noxfile.py
client/**
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "nx-post-support",
"displayName": "NX Postprocessor Support",
"description": "",
"version": "0.4.9",
"version": "0.4.13",
"publisher": "Christoph",
"serverInfo": {
"name": "NX Postprocessor Support",
+25
View File
@@ -514,6 +514,31 @@
"set syslog [MOM_ask_syslog_name]"
]
},
{
"label": "MOM_ask_ude_info",
"kind": "function",
"description": "This command is used to retrieve the information about a user-defined event (UDE) of a specified object.",
"format": "MOM_ask_ude_info object_name object_type <Start/End/\"\">",
"parameters": [
{
"name": "Object_name",
"desc": "The name of the object to which the UDE is attached. It can be a group, an operation, a tool, a geometry, or a method."
},
{
"name": "object_type",
"desc": "The type of the object to which the UDE is attached. The following types are available.(group || operation/oper || tool || geometry/geom || method/meth)"
},
{
"name": "Start/End/\"\"",
"desc": "This parameter is optional. Indicates whether you want to retrieve the UDE attached to the start event, end event, or both. If it is empty, returns the UDEs attached to the specified operation."
}
],
"returns": [
"0 - The UDE information retrieval failed.",
"1 - The UDE information successfully retrieved."
],
"example": []
},
{
"label": "MOM_cancel_suppress_force_once_per_event",
"kind": "function",
-63
View File
@@ -1,63 +0,0 @@
import re
def format_tcl(src: str, indent_str=" ") -> str:
"""
Simple Tcl formatter with special handling for:
1) Single-line 'if {cond} {action}' blocks remain on one line.
2) Combined closing-and-opening lines like '} else {' dedent then re-indent.
3) Lines like '} Tag' stay on the same line: '} Tag'.
4) Standard multi-line blocks for 'if', 'elseif', 'else', '{', '}'.
"""
level = 0
out_lines = []
for raw_line in src.splitlines():
stripped = raw_line.strip()
# Comments are ignored
if stripped.startswith("#"):
out_lines.append(indent_str * level + stripped)
continue
# Single-line 'if {cond} {action}' → no indent change
if re.match(r"^(if|elseif)\s*\{[^}]+\}\s*\{[^}]+\}$", stripped):
out_lines.append(indent_str * level + stripped)
continue
# Combined '} else {' → dedent, print, then indent
if re.match(r"^\}\s*(elseif|else)\b.*\{$", stripped):
level = max(level - 1, 0)
out_lines.append(indent_str * level + stripped)
level += 1
continue
# SPECIAL: closing brace plus tag on same line: '} Tag'
m = re.match(r"^\}\s+(.+)", stripped)
if m and not stripped.startswith("#"):
# close one block
level = max(level - 1, 0)
# stay on one line: "} Tag"
out_lines.append(f"{indent_str * level}}} {m.group(1)}")
continue
# Pure '}' → dedent then print
if stripped == "}":
level = max(level - 1, 0)
out_lines.append(f"{indent_str * level}{stripped}")
continue
# 'elseif' or 'else' alone → align with matching 'if'
if re.match(r"^(elseif|else)\b(?!.*\{)", stripped):
level = max(level - 1, 0)
out_lines.append(f"{indent_str * level}{stripped}")
continue
# Default: print at current indent
out_lines.append(f"{indent_str * level}{stripped}")
# Open a new block on lines ending with '{'
if re.match(r"^(if|elseif)\b.*\{$", stripped) or stripped.endswith("{"):
level += 1
return "\n".join(out_lines)
+9
View File
@@ -21,6 +21,7 @@ class StandardCompletionItems:
self.__tcl_keyword_list = self.__load_tcl_keyword()
self.__nx_procs = self.__load_nx_procs()
self.__nx_variables = self.__load_nx_variables()
self.__custom_functions = list[lsp.CompletionItem]
@property
def json_data(self):
@@ -38,6 +39,14 @@ class StandardCompletionItems:
def nx_variables(self):
return self.__nx_variables
@property
def custom_functions(self) -> list[lsp.CompletionItem]:
return self.__custom_functions
@custom_functions.setter
def custom_functions(self, value: lsp.CompletionItem):
self.__custom_functions.append(value)
def __load_json(self) -> dict:
with open(
pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r"
+67 -38
View File
@@ -4,16 +4,14 @@
from __future__ import annotations
import copy
import json
import os
import pathlib
import re
import sys
import sysconfig
import traceback
from typing import Any, List, Optional, Sequence, Tuple
import re
from typing import Any, List, Optional, Tuple
import operator
from functools import reduce
# **********************************************************
@@ -39,23 +37,18 @@ update_sys_path(
# **********************************************************
# pylint: disable=wrong-import-position,import-error
import lsp_jsonrpc as jsonrpc
import lsp_utils as utils
import lsprotocol.types as lsp
from pygls import server, uris, workspace
from pygls.workspace.text_document import TextDocument
from common.load_data import standard_items
from common.formatter import format_tcl
from tclint.parser import Parser
from tclint.lexer import TclSyntaxError
from tclint.format import Formatter, FormatterOpts
from tclint.violations import Violation
from tools.semantic_tokens import (
SemanticTokenCollector,
collect_semantic_tokens,
encode_tokens,
)
from plugins.poco_plugin import commands
from tools import checks
from tools import checks, parser
from tools.semantic_tokens import _Highlighter, TOKEN_TYPES, TokenModifier
from tools.completion_items import completion
from tools.inlay_hint import InlayHintGenerator
DIAGNOSTIC_SOURCE = "nx-post-support"
@@ -63,7 +56,7 @@ DIAGNOSTIC_SOURCE = "nx-post-support"
class TclLanguageServer(server.LanguageServer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.parser = Parser()
self.parser = parser.CustomParser()
for command in commands:
self.parser._commands.update(command)
self.diagnostics = {}
@@ -78,7 +71,6 @@ class TclLanguageServer(server.LanguageServer):
# parser._commands.update(commands)
indent = "\t" if not options.insert_spaces else " " * options.tab_size
formatter = Formatter(
FormatterOpts(
indent=indent,
@@ -102,7 +94,7 @@ class TclLanguageServer(server.LanguageServer):
self.parser.violations = []
tree = self.parser.parse(document.source)
violations += self.parser.violations
# log_to_output(tree.pretty(2))
for checker in checks.get_checkers():
violations += checker.check(document.source, tree)
return violations
@@ -170,16 +162,6 @@ WORKSPACE_SETTINGS = {}
GLOBAL_SETTINGS = {}
RUNNER = pathlib.Path(__file__).parent / "lsp_runner.py"
TOKEN_TYPES = [
"command",
"variable",
"function",
"string",
"number",
"keyword",
"comment",
]
TOKEN_MODIFIERS = []
MAX_WORKERS = 5
LSP_SERVER = TclLanguageServer(
@@ -207,12 +189,15 @@ def did_open(params: lsp.DidOpenTextDocumentParams) -> None:
"""LSP handler for textDocument/didOpen request."""
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
LSP_SERVER.compute_diagnostics(document)
completion.reset()
tree = LSP_SERVER.parser.parse(document.source)
tree.accept(completion, recurse=True)
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_SAVE)
def did_save(params: lsp.DidSaveTextDocumentParams) -> None:
"""LSP handler for textDocument/didSave request."""
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
_ = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_CLOSE)
@@ -225,6 +210,9 @@ def did_change(params: lsp.DidChangeTextDocumentParams) -> None:
"""LSP handler for textDocument/didChange request"""
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
LSP_SERVER.compute_diagnostics(document)
completion.reset()
tree = LSP_SERVER.parser.parse(document.source)
tree.accept(completion, recurse=True)
@LSP_SERVER.feature(
@@ -254,22 +242,63 @@ def document_diagnostic(params: lsp.DocumentDiagnosticParams):
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_COMPLETION)
def on_completion(params: lsp.CompletionParams) -> list[lsp.CompletionItem]:
_ = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
items = (
standard_items.tcl_keyword_list
+ standard_items.nx_procs
+ standard_items.nx_variables
+ completion.custom_functions
)
return lsp.CompletionList(is_incomplete=False, items=items)
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL)
def on_semantic_tokens(params: lsp.SemanticTokensParams):
doc = LSP_SERVER.workspace.get_document(params.text_document.uri)
code = doc.source
# @LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DOCUMENT_SYMBOL)
# def document_symbols(params: lsp.DocumentSymbolParams):
# doc = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
# return []
tokens = collect_semantic_tokens(code)
data = encode_tokens(tokens)
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_INLAY_HINT)
def inlay_hints(params: lsp.InlayHintParams):
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
tree = LSP_SERVER.parser.parse(document.source)
# collect Inlay Hints
generator = InlayHintGenerator(completion.proc_signatures)
tree.accept(generator, recurse=True)
return generator.hints
@LSP_SERVER.feature(
lsp.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
lsp.SemanticTokensLegend(
token_types=TOKEN_TYPES,
token_modifiers=[m.name for m in TokenModifier],
),
)
def semantic_tokens(params: lsp.SemanticTokensParams):
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
data = []
plugins = []
hl = _Highlighter(plugins, log_to_output=log_to_output)
tree = LSP_SERVER.parser.parse(document.source)
tree.accept(hl, recurse=True)
tokens = hl.tokens()
for token in tokens:
data.extend(
[
token.line,
token.offset,
token.lenght,
TOKEN_TYPES.index(token.tok_type),
reduce(operator.or_, token.tok_modifiers, 0),
]
)
return lsp.SemanticTokens(data=data)
@@ -363,12 +392,12 @@ def formatting(params: lsp.DocumentFormattingParams) -> list[lsp.TextEdit] | Non
start = lsp.Position(line=0, character=0)
last_line = source.rsplit("\n", 1)[-1]
end = lsp.Position(line=source.count("\n"), character=len(last_line))
formatted = LSP_SERVER.format(doc, params.options)
if GLOBAL_SETTINGS.get("formatter", True):
source = LSP_SERVER.format(doc, params.options)
return [
lsp.TextEdit(
range=lsp.Range(start=start, end=end),
new_text=formatted,
new_text=source,
)
]
@@ -401,7 +430,7 @@ def initialize(params: lsp.InitializeParams) -> lsp.InitializeResult:
)
semantic_tokens_legend = lsp.SemanticTokensLegend(
token_types=TOKEN_TYPES,
token_modifiers=TOKEN_MODIFIERS,
token_modifiers=TokenModifier,
)
return lsp.InitializeResult(
capabilities=lsp.ServerCapabilities(
+7
View File
@@ -36,6 +36,12 @@ def lib_ge_command_buffer_edit_prepend(args, parser):
)
def lib_ge_command_buffer_edit_replace(args, parser):
_lib_ge_command_buffer_edit(
args, parser, "LIB_GE_command_buffer_edit_replace", 3, 5
)
def lib_ge_command_buffer_edit_insert(args, parser):
_lib_ge_command_buffer_edit(args, parser, "LIB_GE_command_buffer_edit_insert", 2, 6)
@@ -44,5 +50,6 @@ commands = [
{"LIB_GE_command_buffer_edit_append": lib_ge_command_buffer_edit_append},
{"LIB_GE_command_buffer_edit_prepend": lib_ge_command_buffer_edit_prepend},
{"LIB_GE_command_buffer_edit_insert": lib_ge_command_buffer_edit_insert},
{"LIB_GE_command_buffer_edit_replace": lib_ge_command_buffer_edit_replace},
{"LIB_GE_command_buffer": _lib_ge_command_buffer},
]
+11 -34
View File
@@ -1,7 +1,5 @@
from tclint.parser import Parser as tcLintParser
from tclint.lexer import Lexer as tclingLexer
from parser.lexer import Lexer
from parser.parser import Parser
def main():
@@ -28,22 +26,9 @@ LIB_SPF_prepend MOM_strt Start_Lib {
def lexer_test():
lexer = tclingLexer()
tree = lexer.input("""puts hello
proc myProc {arg {arg7 0}} {}
set myVar 123
puts puts
LIB_SPF_prepend MOM_strt Start_Lib {
set somthing 1
set more 2
} myTag
LIB_SPF_prepend MOM_strt Start_Lib {
proc test {} {
puts "Hello"
}
set somthing 1
set someting 3
} myTag""")
tree = lexer.input("""
if {$oem(custom_clamp_4th) == 1 && $oem(custom_clamp_5th) == 1 \\
&& $oem(status_clamp_4th) == "off" && $oem(status_clamp_5th) == "off"}""")
# print("Lexing input:\n", code)
# print("\nTokens:\n" + "-" * 30)
@@ -57,26 +42,18 @@ LIB_SPF_prepend MOM_strt Start_Lib {
def test_1():
code = """
proc myProc {arg } {
set myVar 1
}
from tclint.lexer import Lexer, TOK_BACKSLASH_NEWLINE
namespace eval myNS {
proc innerProc {} {
MOM_abort_program "Test"
}
}
set result [myNS::innerProc]
"""
code = "expr {1 == 2 \\\n&& 3 == 4}"
lexer = Lexer()
lexer.input(code)
parser = Parser(lexer)
ast = parser.parse()
visitor = NodeVisitor()
ast.accept(visitor)
while lexer.type() is not None:
print(
f"Type: {lexer.type():<20} | Value: {lexer.value()!r} | Pos: {lexer.pos()}"
)
lexer.next()
class NodeVisitor:
@@ -98,4 +75,4 @@ class NodeVisitor:
if __name__ == "__main__":
main()
test_1()
+54 -1
View File
@@ -1,4 +1,57 @@
from enum import Enum
from tclint.syntax_tree import Visitor, BareWord, Command
from tclint.violations import Violation, Rule
class Rules(Enum):
VALIDATION = "validation"
OPTIONAL_ARG_POSITION = "optinal_args"
def __str__(self):
return self.value
class CommandArgsCheck(Visitor):
def __init__(self):
self._violations = []
def check(self, _, tree):
self._violations.clear()
tree.accept(self, recurse=True)
return self._violations
def visit_command(self, command: Command):
if (
not hasattr(command.routine, "contents")
or command.routine.contents != "proc"
):
return
if len(command.args) < 2:
return
args_node = command.args[1]
if not hasattr(args_node, "children"):
return
found_optional = False
for arg in args_node.children:
# Required argument
if isinstance(arg, BareWord):
if found_optional:
self._violations.append(
Violation(
Rules.OPTIONAL_ARG_POSITION,
"Required argument follows optional one",
arg.pos,
arg.end_pos,
)
)
# Optional argument
elif hasattr(arg, "children") and len(arg.children) >= 2:
found_optional = True
def get_checkers():
checkers = ()
checkers = (CommandArgsCheck(),)
return checkers
+68
View File
@@ -0,0 +1,68 @@
from tclint.syntax_tree import Visitor, Command, BareWord, List
import lsprotocol.types as lsp
class CompletionItems:
def __init__(self):
self._custom_functions: list[lsp.CompletionItem] = []
@property
def custom_functions(self) -> list[lsp.CompletionItem]:
return self._custom_functions
@custom_functions.setter
def custom_functions(self, value: lsp.CompletionItem):
self._custom_functions.append(value)
class _Completion(Visitor):
def __init__(self):
super().__init__()
self._custom_functions: list[lsp.CompletionItem] = []
self._proc_signatures = {}
@property
def custom_functions(self) -> list[lsp.CompletionItem]:
return self._custom_functions
@property
def proc_signatures(self):
return self._proc_signatures
def reset(self):
self._custom_functions = []
self._proc_signatures = {}
def visit_command(self, command: Command):
routine = command.routine
if routine.contents == "proc" and command.args:
first_arg = command.args[0]
if hasattr(first_arg, "value") and not any(
item.label == first_arg.value for item in self._custom_functions
):
self._custom_functions.append(
lsp.CompletionItem(
label=first_arg.value, kind=lsp.CompletionItemKind.Function
)
)
if len(command.args) < 2:
return
param_list_node = command.args[1]
if not hasattr(param_list_node, "children"):
return
param_names = []
for arg in param_list_node.children:
if isinstance(arg, BareWord):
param_names.append(arg.value)
elif isinstance(arg, List) and len(arg.children) >= 1:
first = arg.children[0]
if isinstance(first, BareWord):
param_names.append(first.value)
self._proc_signatures[first_arg.value] = param_names
completion = _Completion()
+29
View File
@@ -0,0 +1,29 @@
import lsprotocol.types as lsp
from tclint.syntax_tree import Visitor, Command
class InlayHintGenerator(Visitor):
def __init__(self, proc_signatures):
self.proc_signatures = proc_signatures
self.hints = []
def visit_command(self, command: Command):
name = getattr(command.routine, "contents", None)
if name not in self.proc_signatures:
return
param_names = self.proc_signatures[name]
for idx, arg in enumerate(command.args):
if idx >= len(param_names):
break
param_name = param_names[idx]
if arg.pos:
line, col = arg.pos
self.hints.append(
lsp.InlayHint(
position=lsp.Position(line=line - 1, character=col - 1),
label=f"{param_name}:",
kind=lsp.InlayHintKind.Parameter,
)
)
+64 -40
View File
@@ -1,49 +1,73 @@
import textwrap
import io
from typing import Optional, Tuple
from tclint.parser import Parser
from tclint.lexer import (
STATE_BRACEDWORD,
TOK_LBRACE,
TOK_EOF,
TOK_RBRACE,
TclSyntaxError,
from tclint.commands import CommandArgError
from tclint.syntax_tree import (
BracedWord,
BareWord,
BracedExpression,
List,
QuotedWord,
Expression,
)
from tclint.syntax_tree import BracedWord
from tclint.lexer import TclSyntaxError, Lexer, TOK_EOF
class CustomParser(Parser):
def parse_braced_word(self, ts):
"""
Ersetzt BracedWord durch echtes Script, wenn mehrzeilig.
"""
def __init__(self, debug=False, command_plugins=None):
super().__init__(debug, command_plugins)
# Used to normalize newlines consistently with open()'s universal newlines mode.
self._decoder = io.IncrementalNewlineDecoder(None, True)
def parse(self, script: str, pos: Optional[Tuple[int, int]] = None):
script = self._decoder.decode(script, True)
lexer = Lexer(pos=pos)
lexer.input(script)
tree = self._parse_script(lexer, in_command_sub=False)
assert lexer.type() == TOK_EOF, (
"Didn't reach EOF parsing script, please file a bug report."
)
return tree
def _parse_operator(self, ts):
pos = ts.pos()
ts.lexer.push_state(STATE_BRACEDWORD)
ts.assert_(TOK_LBRACE)
content = ""
expected = [pos]
while True:
t = ts.type()
if t == TOK_EOF:
raise TclSyntaxError(
"reached EOF without finding match for brace",
expected[-1],
ts.pos(),
)
if t == TOK_LBRACE:
expected.append(ts.pos())
elif t == TOK_RBRACE:
expected.pop()
if not expected:
ts.lexer.pop_state()
ts.next()
break
content += ts.value()
# hacky logic to handle parsing legal operators
if ts.value() in {"*", "&", "|"}:
# one or two of these characters are legal operators
operator = ts.value()
ts.next()
if ts.value() == operator:
operator += ts.value()
ts.next()
elif ts.value() in {"<", ">"}:
operator = ts.value()
ts.next()
if ts.value() in {operator, "="}:
operator += ts.value()
ts.next()
elif ts.value() in {"=", "!"}:
operator = ts.value()
ts.next()
if ts.value() != "=":
raise TclSyntaxError(
f"invalid operator in expression: {operator}", pos, ts.pos()
)
operator += ts.value()
ts.next()
elif ts.value() in {"*", "/", "%", "+", "-", "^", "eq", "ne", "in", "ni"}:
operator = ts.value()
ts.next()
else:
message = "invalid operator in expression: "
if ts.value() == "\\ ":
message += (
"\\ (check for trailing whitespace if it's the end of the line)"
)
else:
message += ts.value()
raise TclSyntaxError(message, pos, ts.pos())
end_pos = ts.pos()
# Mehrzeilig? Dann als Script parsen:
if "\n" in content.strip():
self.parse_script(content)
# Einzeilig: unverändert als Literal
return BracedWord(content, pos=pos, end_pos=end_pos)
return BareWord(operator, pos=pos, end_pos=ts.pos())
+185 -62
View File
@@ -1,76 +1,199 @@
from tclint.parser import Parser
from tclint.syntax_tree import Visitor, BareWord, VarSub, Comment, Command, Function
TOKEN_TYPES = {
"command": 0,
"variable": 1,
"function": 2,
"string": 3,
"number": 4,
"keyword": 5,
"comment": 6,
}
import enum
from typing import List
from tclint.syntax_tree import Visitor, QuotedWord, Command, BareWord
from tclint.commands import get_commands
import attrs
from common.load_data import standard_items
from tools.completion_items import completion
class SemanticTokenCollector(Visitor):
def __init__(self):
self.tokens = []
class TokenModifier(enum.IntFlag):
deprecated = enum.auto()
readonly = enum.auto()
defaultLibrary = enum.auto()
definition = enum.auto()
declaration = enum.auto()
builtin = enum.auto()
def _add_token(self, node, token_type):
if not node.pos or not node.end_pos:
@attrs.define
class Token:
line: int
offset: int
lenght: int
tok_type: str = ""
tok_modifiers: List[TokenModifier] = attrs.field(factory=list)
TOKEN_TYPES = [
"keyword",
"variable",
"function",
"operator",
"parameter",
"type",
"class",
"string",
"parameter",
]
class _Highlighter(Visitor):
def __init__(self, plugins, log_to_output):
self._commands = get_commands(plugins)
self._tokens = []
self.log_to_output = log_to_output
def _get_token_info(self, node):
"""Hilfsmethode um Token-Informationen aus verschiedenen Node-Typen zu extrahieren."""
if not hasattr(node, "pos"):
return None
# Einfacher Fall: Node hat direkten value
if hasattr(node, "value") and node.value is not None:
line, col = node.pos
return (line - 1, col - 1), len(node.value)
# CompoundBareWord: versuche erstes Segment
if hasattr(node, "children") and node.children:
first_segment = node.children[0]
if (
hasattr(first_segment, "value")
and first_segment.value is not None
and hasattr(first_segment, "pos")
):
line, col = first_segment.pos
return (line - 1, col - 1), len(first_segment.value)
# Fallback: Gesamtlänge aus Positionen berechnen
if hasattr(node, "end_pos"):
start_line, start_col = node.pos
end_line, end_col = node.end_pos
if start_line == end_line:
length = end_col - start_col
return (start_line - 1, start_col - 1), length
return None
def visit_quoted_word(self, word: QuotedWord):
if not word.contents:
return
line, col = node.pos
end_line, end_col = node.end_pos
length = (end_col - col) if line == end_line else 1
self.tokens.append((line - 1, col - 1, length, token_type, 0))
def visit_command(self, command: Command):
self._add_token(command.routine, "command")
for arg in command.args:
arg.accept(self, recurse=True)
def visit_comment(self, comment: Comment):
self._add_token(comment, "comment")
line, col = word.contents_pos
self._tokens.append(((line - 1, col - 1), len(word.contents), "string", []))
pass
def visit_bare_word(self, word: BareWord):
if word.value.isdigit():
self._add_token(word, "number")
else:
self._add_token(word, "string")
if any(item.label == word.value for item in standard_items.nx_procs) or any(
item.label == word.value for item in completion.custom_functions
):
line, col = word.pos
self._tokens.append(
(((line - 1, col - 1), len(word.value), "function", []))
)
def visit_var_sub(self, var_sub: VarSub):
self._add_token(var_sub, "variable")
def visit_command(self, command: Command):
routine = command.routine
def visit_function(self, function: Function):
self._add_token(function.name, "function")
for arg in function.args:
arg.accept(self, recurse=True)
if routine.contents == "puts":
line, col = routine.contents_pos
self._tokens.append(
(
(
(line - 1, col - 1),
len(routine.contents),
"function",
[TokenModifier.builtin],
)
)
)
if routine.contents == "set" and command.args:
first_arg = command.args[0]
token_info = self._get_token_info(first_arg)
if token_info:
(line, col), length = token_info
self._tokens.append(
(
(
(line, col),
length,
"variable",
[TokenModifier.declaration],
)
)
)
if routine.contents == "proc" and command.args:
first_arg = command.args[0]
if hasattr(first_arg, "pos") and hasattr(first_arg, "value"):
line, col = first_arg.pos
self._tokens.append(
(
(
(line - 1, col - 1),
len(first_arg.value),
"function",
[TokenModifier.declaration],
)
)
)
if len(command.args) >= 2:
param_list = command.args[1]
def collect_semantic_tokens(code: str):
parser = Parser()
tree = parser.parse(code)
visitor = SemanticTokenCollector()
tree.accept(visitor, recurse=True)
return visitor.tokens
# BracedWord oder Liste erwartet
if hasattr(param_list, "children"):
for child in param_list.children:
# Parameter kann einfaches Wort sein
if hasattr(child, "value") and child.value is not None:
line, col = child.pos
self._tokens.append(
(
(line - 1, col - 1),
len(child.value),
"parameter",
[TokenModifier.declaration],
)
)
# Parameter mit Default-Wert ist meist eine List (z.B. {arg default})
elif hasattr(child, "children") and len(child.children) >= 1:
name_node = child.children[0]
if hasattr(name_node, "value") and hasattr(
name_node, "pos"
):
line, col = name_node.pos
self._tokens.append(
(
(line - 1, col - 1),
len(name_node.value),
"parameter",
[TokenModifier.declaration],
)
)
if routine.contents == "namespace" and command.args:
first_arg = command.args[1]
if hasattr(first_arg, "pos") and first_arg.value is not None:
line, col = first_arg.pos
self._tokens.append(
(((line - 1, col - 1), len(first_arg.value), "class", []))
)
def encode_tokens(tokens):
tokens.sort()
encoded = []
def tokens(self) -> list[Token]:
"""Encode tokens as described in
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens.
"""
tokens = []
last_line = 0
last_col = 0
for (line, col), length, tok_type, tok_modifier in sorted(
self._tokens, key=lambda x: x[0]
):
line_delta = line - last_line
col_delta = col
if line == last_line:
col_delta -= last_col
last_line = 0
last_char = 0
tokens.append(Token(line_delta, col_delta, length, tok_type, tok_modifier))
last_line, last_col = line, col
for line, char, length, token_type, modifiers in tokens:
delta_line = line - last_line
delta_start = char - last_char if delta_line == 0 else char
encoded.extend([delta_line, delta_start, length, token_type, modifiers])
last_line = line
last_char = char if delta_line == 0 else 0
return encoded
return tokens
+110 -7
View File
@@ -1,13 +1,116 @@
proc myProc {arg {opt 1}} {
set ::custom_flag(from_move,$::mom_path_name) 1
# set ::custom_flag(from_move,$::mom_path_name) 1
set te875st 11111
#set ::custom_flag(from_move,$::mom_path_name) 1
if {$main == 1 && 1 == 1} {
puts "main"
}
set myVar 1
proc test {} {
puts "main"
proc llll {} {}
set rrrrrrr
}
LIB_GE_command_buffer_edit_insert MOM_tool_change_LIB TOOL_CHANGE_AUTO {CUSTOM_after_tool_change_call} mytag after @TOOL_CHANGE_AUTO
namespace eval myNameSpace {
proc namespaceProc {} {}
MOM_abort
namespace eval myns {
proc add {a b} {
set sum [expr {$a + $b}]
return $sum
}
set config "debug"
}
set result [myNameSpace::namespaceProc]
#_________________________________________________________________________________________________
# <Documentation>
# Function to output a spacer line or empty line
#_________________________________________________________________________________________________
proc SERVICE_spacer_output {type {length 20} {line_num 0} {output 1}} {
LIB_GE_message [string repeat $type $length] "output_$output" $line_num
}
MOM_abort_program "Test"
SERVICE_spacer_output "*" 2 0 0
#_________________________________________________________________________________________________
# <Documentation>
# Function to delete the file
#_________________________________________________________________________________________________
proc SERVICE_remove_file {file} {
if {![SERVICE_check_file_exists $file]} {return}
MOM_remove_file $file
}
#_________________________________________________________________________________________________
# <Documentation>
# Function to check if the file exists
#_________________________________________________________________________________________________
proc SERVICE_check_file_exists {file} {
if {[file exists $file]} {return 1}
return 0
}
#_________________________________________________________________________________________________
# <Documentation>
# Ask UDE Info for the Tool
#_________________________________________________________________________________________________
proc SERVICE_ask_ude_tool {pos ude_name tool_name} {
MOM_ask_ude_info $tool_name "tool" $pos
if {[lsearch $::mom_result $ude_name] != -1} {
return 1
}
return 0
}
#_________________________________________________________________________________________________
# <Documentation>
# Ask UDE Info for the Operation
#_________________________________________________________________________________________________
proc SERVICE_ask_ude_operation {pos ude_name path_name} {
MOM_ask_ude_info $path_name "operation" $pos
if {[lsearch $::mom_result $ude_name] != -1} {
return 1
}
return 0
}
#_________________________________________________________________________________________________
# <Documentation>
# output suppress or dont suppress
# [SERVICE_output_handling "ingore_output"] ignores the output
# arg options: ingore_output
# restore
#_________________________________________________________________________________________________
proc SERVICE_output_handling {handler} {
set ::lib_ge(hidden_output) $handler
}
#_________________________________________________________________________________________________
# <Documentation>
# write the mom_tool_data to store tool information
# this function is called in start of program
#_________________________________________________________________________________________________
proc SERVICE_get_tool_data {} {
global mom_tool_data
global mom_operation_info
set mom_tool_data(toollist) ""
set operations $::mom_operation_name_list
foreach operation $operations {
if {[lsearch -exact $mom_tool_data(toollist) $mom_operation_info($operation,tool_name)] == -1} {
lappend mom_tool_data(toollist) $mom_operation_info($operation,tool_name)
}
}
}
LIB_GE_command_buffer_edit_replace MOM_end_of_program_LIB END_OF_PROGRAM @END_OF_PROG {
MOM_do_template "end_of_program_rewind"
} EndOfProgramRewind