Compare commits
67
Commits
0.4.10
...
2025.8.202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
579847b56d | ||
|
|
618d6b478b | ||
|
|
cf62044d3e | ||
|
|
aeebb5c964 | ||
|
|
f659db1147 | ||
|
|
b059a75205 | ||
|
|
27e2b3bf6e | ||
|
|
f55c3ba904 | ||
|
|
cb430a5553 | ||
|
|
637a28ad82 | ||
|
|
499df9b51d | ||
|
|
31ccae9a40 | ||
|
|
9b373e85b7 | ||
|
|
0c5f4ea6fb | ||
|
|
793b35f39f | ||
|
|
3780fc27dc | ||
|
|
6541830ef6 | ||
|
|
509b13e99f | ||
|
|
396cfdc4ec | ||
|
|
ccd22e4343 | ||
|
|
7d83ae1012 | ||
|
|
029eaebd57 | ||
|
|
2a2db8f217 | ||
|
|
8be9aae05d | ||
|
|
6bded783a1 | ||
|
|
f5c61267e3 | ||
|
|
b680c01989 | ||
|
|
1abf89fe56 | ||
|
|
6a051a6dee | ||
|
|
d22d897d23 | ||
|
|
7c834176a3 | ||
|
|
72be83c282 | ||
|
|
fa5ea00384 | ||
|
|
a8b4fd9d1e | ||
|
|
35d0f20fba | ||
|
|
440ebc4df5 | ||
|
|
1b48cc1983 | ||
|
|
6cde05b786 | ||
|
|
cde58db3d7 | ||
|
|
3613391c23 | ||
|
|
894a5b8b52 | ||
|
|
c644ac9bd3 | ||
|
|
fd5b1f2e71 | ||
|
|
57c65221d3 | ||
|
|
528c7a6b9e | ||
|
|
2d034838b4 | ||
|
|
f0ed8ac708 | ||
|
|
6a30c1ab53 | ||
|
|
6d404f26f2 | ||
|
|
afc675f47f | ||
|
|
d2936ec5f6 | ||
|
|
8b44b41d68 | ||
|
|
5124c12bc6 | ||
|
|
32061809a7 | ||
|
|
f762c42e00 | ||
|
|
4ccdf9e9f3 | ||
|
|
b86d4f7b58 | ||
|
|
0318532308 | ||
|
|
5e2e85830b | ||
|
|
98e2d104a8 | ||
|
|
44fd772151 | ||
|
|
d90f9b5425 | ||
|
|
fed8b84b46 | ||
|
|
ed045aa459 | ||
|
|
8da61abfce | ||
|
|
0ea2cbec87 | ||
|
|
d99d7c6bf6 |
@@ -1,7 +1,6 @@
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*"
|
||||
release:
|
||||
types: [published]
|
||||
jobs:
|
||||
build_and_publish:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -35,7 +34,22 @@ jobs:
|
||||
git add package.json
|
||||
git commit -m "Update version to ${{ github.ref_name }}"
|
||||
git push origin HEAD:main
|
||||
- name: Publish to Visual Studio Marketplace
|
||||
- name: Publish to Visual Studio Marketplace (Pre-Release)
|
||||
if: ${{ github.event.release.prerelease }}
|
||||
run: vsce publish --pre-release
|
||||
env:
|
||||
VSCE_PAT: ${{ secrets.VSCODE_MARKETPALCE }}
|
||||
- name: Publish to Visual Studio Marketplace (Stable)
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
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
|
||||
|
||||
Vendored
+2
-1
@@ -2,6 +2,7 @@
|
||||
"ruff.configuration": {
|
||||
"lint": {
|
||||
"extend-ignore": ["F821", "E402"]
|
||||
}
|
||||
},
|
||||
"line-length": 200
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -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/**
|
||||
@@ -0,0 +1,49 @@
|
||||
# NX Postprocessor Support
|
||||
|
||||
A comprehensive VS Code extension providing language support for NX CAD postprocessor development, including CDL (Cutter Definition Language), TCL, and DEF files.
|
||||
|
||||
## Features
|
||||
|
||||
- **Syntax Highlighting** - Full syntax highlighting for CDL, TCL, and DEF files
|
||||
- **Code Formatting** - Automatic code formatting for all supported file types
|
||||
- **Multi-language Support** - Supports NX CDL, TCL, and DEF file formats
|
||||
- **Intelligent Code Analysis** - Linting and error detection for postprocessor code
|
||||
- **Auto-completion** - Context-aware code completion for faster development
|
||||
|
||||
## Supported File Types
|
||||
|
||||
- `.cdl` - NX Cutter Definition Language files
|
||||
- `.tcl` - NX TCL postprocessor files
|
||||
- `.def` - NX Definition files
|
||||
|
||||
## Installation
|
||||
|
||||
1. Install from the VS Code Marketplace
|
||||
2. Open any `.cdl`, `.tcl`, or `.def` file
|
||||
3. The extension will automatically activate and provide language support
|
||||
|
||||
## Configuration
|
||||
|
||||
The extension can be configured through VS Code settings:
|
||||
|
||||
- `nx-post-support.interpreter` - Specify custom Python interpreter path for the language server
|
||||
- `nx-post-support.formatter` - Enable/disable the TCL formatter (default: false)
|
||||
- `nx-post-support.inlayHint` - Enable/disable inlay Hints (default: true)
|
||||
|
||||
## Usage
|
||||
|
||||
Simply open any supported file type and enjoy:
|
||||
|
||||
- Syntax highlighting
|
||||
- Error detection and linting
|
||||
- Code completion
|
||||
- Code formatting (Format Document command)
|
||||
- Hover information
|
||||
|
||||
## Contributing
|
||||
|
||||
This extension is actively maintained. For issues or feature requests, please visit our [repository](https://git.cbsk-tech.de/Christoph/nx_post_support.git).
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see LICENSE file for details.
|
||||
|
||||
@@ -79,7 +79,8 @@ export async function getWorkspaceSettings(
|
||||
interpreter: resolveVariables(interpreter, workspace),
|
||||
importStrategy: config.get<string>(`importStrategy`) ?? "useBundled",
|
||||
showNotifications: config.get<string>(`showNotifications`) ?? "off",
|
||||
formatter: config.get<boolean>(`formatter`) ?? true
|
||||
formatter: config.get<boolean>(`formatter`) ?? true,
|
||||
inlayHint: config.get<boolean>(`inlayHint`) ?? true
|
||||
}
|
||||
return workspaceSetting
|
||||
}
|
||||
@@ -111,7 +112,8 @@ export async function getGlobalSettings(
|
||||
interpreter: interpreter,
|
||||
importStrategy: getGlobalValue<string>(config, "importStrategy", "useBundled"),
|
||||
showNotifications: getGlobalValue<string>(config, "showNotifications", "off"),
|
||||
formatter: config.get<boolean>(`formatter`) ?? true
|
||||
formatter: config.get<boolean>(`formatter`) ?? true,
|
||||
inlayHint: config.get<boolean>(`inlayHint`) ?? true
|
||||
}
|
||||
return setting
|
||||
}
|
||||
@@ -126,7 +128,8 @@ export function checkIfConfigurationChanged(
|
||||
`${namespace}.interpreter`,
|
||||
`${namespace}.importStrategy`,
|
||||
`${namespace}.showNotifications`,
|
||||
`${namespace}.formatter`
|
||||
`${namespace}.formatter`,
|
||||
`${namespace}.inlayHint`
|
||||
]
|
||||
const changed = settings.map((s) => e.affectsConfiguration(s))
|
||||
return changed.includes(true)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
+7
-1
@@ -2,8 +2,9 @@
|
||||
"name": "nx-post-support",
|
||||
"displayName": "NX Postprocessor Support",
|
||||
"description": "",
|
||||
"version": "0.4.9",
|
||||
"version": "2025.8.201",
|
||||
"publisher": "Christoph",
|
||||
"icon": "images/nx-1.png",
|
||||
"serverInfo": {
|
||||
"name": "NX Postprocessor Support",
|
||||
"module": "nx-post-support"
|
||||
@@ -84,6 +85,11 @@
|
||||
"default": false,
|
||||
"description": "Use the TCL formatter from `NX Postprocessor Support`"
|
||||
},
|
||||
"nx-post-support.inlayHint": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Use the Inlay Hints in from `NX Postprocessor Support`"
|
||||
},
|
||||
"nx-post-support.importStrategy": {
|
||||
"default": "useBundled",
|
||||
"description": "Defines where `NX Postprocessor Support` is imported from.",
|
||||
|
||||
@@ -32,7 +32,7 @@ if debugger_path:
|
||||
# This will ensure that execution is paused as soon as the debugger
|
||||
# connects to VS Code. If you don't want to pause here comment this
|
||||
# line and set breakpoints as appropriate.
|
||||
debugpy.breakpoint()
|
||||
# debugpy.breakpoint()
|
||||
|
||||
SERVER_PATH = os.fspath(pathlib.Path(__file__).parent / "lsp_server.py")
|
||||
# NOTE: Set breakpoint in `lsp_server.py` before continuing.
|
||||
|
||||
@@ -71,6 +71,10 @@
|
||||
{
|
||||
"label": "expr",
|
||||
"kind": "keyword"
|
||||
},
|
||||
{
|
||||
"label": "return",
|
||||
"kind": "keyword"
|
||||
}
|
||||
],
|
||||
"MOM_procs": [
|
||||
@@ -514,6 +518,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",
|
||||
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
+106
-172
@@ -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,152 +37,23 @@ 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 pygls import uris, workspace
|
||||
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
|
||||
|
||||
DIAGNOSTIC_SOURCE = "nx-post-support"
|
||||
|
||||
|
||||
class TclLanguageServer(server.LanguageServer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.parser = Parser()
|
||||
for command in commands:
|
||||
self.parser._commands.update(command)
|
||||
self.diagnostics = {}
|
||||
|
||||
def format(
|
||||
self,
|
||||
document: TextDocument,
|
||||
options: lsp.FormattingOptions,
|
||||
range: Optional[Tuple[int, int]] = None,
|
||||
):
|
||||
# parser = Parser(command_plugins=["nx_plugins.poco_plugin.py"])
|
||||
# parser._commands.update(commands)
|
||||
|
||||
indent = "\t" if not options.insert_spaces else " " * options.tab_size
|
||||
|
||||
formatter = Formatter(
|
||||
FormatterOpts(
|
||||
indent=indent,
|
||||
spaces_in_braces=False,
|
||||
max_blank_lines=500,
|
||||
indent_namespace_eval=True,
|
||||
),
|
||||
)
|
||||
|
||||
if range is not None:
|
||||
start, end = range
|
||||
return formatter.format_partial(document.source[start:end], self.parser)
|
||||
|
||||
return formatter.format_top(document.source, self.parser)
|
||||
|
||||
def linter(
|
||||
self,
|
||||
document: TextDocument,
|
||||
) -> List[Violation]:
|
||||
violations = []
|
||||
self.parser.violations = []
|
||||
tree = self.parser.parse(document.source)
|
||||
violations += self.parser.violations
|
||||
|
||||
for checker in checks.get_checkers():
|
||||
violations += checker.check(document.source, tree)
|
||||
return violations
|
||||
|
||||
def lint(self, document: TextDocument):
|
||||
diagnostics = []
|
||||
|
||||
try:
|
||||
violations = self.linter(document)
|
||||
except TclSyntaxError as e:
|
||||
return [
|
||||
lsp.Diagnostic(
|
||||
message=str(e),
|
||||
severity=lsp.DiagnosticSeverity.Error,
|
||||
range=lsp.Range(
|
||||
start=lsp.Position(e.start[0] - 1, e.start[1] - 1),
|
||||
end=lsp.Position(e.end[0] - 1, e.end[1] - 1),
|
||||
),
|
||||
code="syntax error",
|
||||
source=DIAGNOSTIC_SOURCE,
|
||||
)
|
||||
]
|
||||
|
||||
for violation in violations:
|
||||
message = violation.message
|
||||
severity = lsp.DiagnosticSeverity.Warning
|
||||
start = lsp.Position(
|
||||
line=violation.start[0] - 1, character=violation.start[1] - 1
|
||||
)
|
||||
end = lsp.Position(
|
||||
line=violation.end[0] - 1, character=violation.end[1] - 1
|
||||
)
|
||||
|
||||
diagnostics.append(
|
||||
lsp.Diagnostic(
|
||||
message=message,
|
||||
severity=severity,
|
||||
range=lsp.Range(
|
||||
start=start,
|
||||
end=end,
|
||||
),
|
||||
code=violation.id,
|
||||
source=DIAGNOSTIC_SOURCE,
|
||||
)
|
||||
)
|
||||
|
||||
return diagnostics
|
||||
|
||||
def _compute_diagnostics(self, document: TextDocument) -> List[lsp.Diagnostic]:
|
||||
return self.lint(document)
|
||||
|
||||
def compute_diagnostics(self, document: TextDocument):
|
||||
# `None` sentinel ensures that `diagnostics` gets updated if the URI is not
|
||||
# present.
|
||||
_, previous = self.diagnostics.get(document, (0, None))
|
||||
|
||||
diagnostics = self._compute_diagnostics(document)
|
||||
|
||||
# Only update if the list has changed
|
||||
if previous != diagnostics:
|
||||
self.diagnostics[document.uri] = (document.version, diagnostics)
|
||||
from tools.semantic_tokens import _Highlighter, TOKEN_TYPES, TokenModifier
|
||||
from tools.completion_items import completion, remove_existing_items, remove_shared_keys
|
||||
from tools.inlay_hint import InlayHintGenerator
|
||||
from tools.file_sourcing import get_all_psc_files, read_psc_file
|
||||
from lsp_tclserver import TclLanguageServer
|
||||
|
||||
|
||||
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(
|
||||
name="NX Postprocessor Support", version="0.0.1", max_workers=MAX_WORKERS
|
||||
)
|
||||
LSP_SERVER = TclLanguageServer(name="NX Postprocessor Support", version="0.0.1", max_workers=MAX_WORKERS)
|
||||
|
||||
# **********************************************************
|
||||
# Tool specific code goes below this.
|
||||
@@ -212,7 +81,7 @@ def did_open(params: lsp.DidOpenTextDocumentParams) -> None:
|
||||
@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 +94,7 @@ 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)
|
||||
LSP_SERVER.update_poco_completion_for_file(document)
|
||||
|
||||
|
||||
@LSP_SERVER.feature(
|
||||
@@ -254,22 +124,69 @@ def document_diagnostic(params: lsp.DocumentDiagnosticParams):
|
||||
|
||||
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_COMPLETION)
|
||||
def on_completion(params: lsp.CompletionParams) -> list[lsp.CompletionItem]:
|
||||
items = (
|
||||
standard_items.tcl_keyword_list
|
||||
+ standard_items.nx_procs
|
||||
+ standard_items.nx_variables
|
||||
)
|
||||
_ = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
|
||||
poco = []
|
||||
|
||||
for key, value in LSP_SERVER.poco_completion.items():
|
||||
poco.extend(value)
|
||||
items = standard_items.tcl_keyword_list + standard_items.nx_procs + standard_items.nx_variables + poco
|
||||
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)
|
||||
# ast = LSP_SERVER.parser.parse(doc.source)
|
||||
# symbols = LSP_SERVER.extract_tcl_symbols(ast)
|
||||
|
||||
tokens = collect_semantic_tokens(code)
|
||||
data = encode_tokens(tokens)
|
||||
# return symbols
|
||||
|
||||
|
||||
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_INLAY_HINT)
|
||||
def inlay_hints(params: lsp.InlayHintParams):
|
||||
log_to_output(str(GLOBAL_SETTINGS.get("inlayHint", False)))
|
||||
if not GLOBAL_SETTINGS.get("inlayHint", False):
|
||||
return []
|
||||
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
|
||||
tree = LSP_SERVER.parser.parse(document.source)
|
||||
|
||||
# collect Inlay Hints
|
||||
hints = []
|
||||
for key, value in LSP_SERVER.proc_signatures.items():
|
||||
generator = InlayHintGenerator(LSP_SERVER.proc_signatures[key])
|
||||
tree.accept(generator, recurse=True)
|
||||
hints += generator.hints
|
||||
return 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, LSP_SERVER.poco_completion)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -307,9 +224,7 @@ def hover(params: lsp.HoverParams) -> lsp.Hover:
|
||||
label = match.get("label", "")
|
||||
|
||||
parameters = match.get("parameters", [])
|
||||
param_lines = (
|
||||
"\n".join(f"- `{p['name']}`: {p['desc']}" for p in parameters) or "_None_"
|
||||
)
|
||||
param_lines = "\n".join(f"- `{p['name']}`: {p['desc']}" for p in parameters) or "_None_"
|
||||
|
||||
example_data = match.get("example", [])
|
||||
example_md = "\n".join(f"{line}" for line in example_data)
|
||||
@@ -363,12 +278,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,
|
||||
)
|
||||
]
|
||||
|
||||
@@ -393,26 +308,46 @@ def initialize(params: lsp.InitializeParams) -> lsp.InitializeResult:
|
||||
|
||||
settings = params.initialization_options["settings"]
|
||||
_update_workspace_settings(settings)
|
||||
log_to_output(
|
||||
f"Settings used to run Server:\r\n{json.dumps(settings, indent=4, ensure_ascii=False)}\r\n"
|
||||
)
|
||||
log_to_output(
|
||||
f"Global settings:\r\n{json.dumps(GLOBAL_SETTINGS, indent=4, ensure_ascii=False)}\r\n"
|
||||
)
|
||||
log_to_output(f"Settings used to run Server:\r\n{json.dumps(settings, indent=4, ensure_ascii=False)}\r\n")
|
||||
log_to_output(f"Global settings:\r\n{json.dumps(GLOBAL_SETTINGS, indent=4, ensure_ascii=False)}\r\n")
|
||||
semantic_tokens_legend = lsp.SemanticTokensLegend(
|
||||
token_types=TOKEN_TYPES,
|
||||
token_modifiers=TOKEN_MODIFIERS,
|
||||
token_modifiers=TokenModifier,
|
||||
)
|
||||
return lsp.InitializeResult(
|
||||
capabilities=lsp.ServerCapabilities(
|
||||
document_formatting_provider=GLOBAL_SETTINGS.get("formatter", True),
|
||||
semantic_tokens_provider=lsp.SemanticTokensOptions(
|
||||
legend=semantic_tokens_legend, full=True, range=False
|
||||
),
|
||||
semantic_tokens_provider=lsp.SemanticTokensOptions(legend=semantic_tokens_legend, full=True, range=False),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@LSP_SERVER.feature(lsp.INITIALIZED)
|
||||
def initialized(params: lsp.InitializedParams):
|
||||
root = LSP_SERVER.workspace.root_path
|
||||
psc_files = get_all_psc_files(pathlib.Path(root))
|
||||
for psc_file in psc_files:
|
||||
poco_files = read_psc_file(psc_file)
|
||||
for sourced_layer in poco_files:
|
||||
completion.reset()
|
||||
try:
|
||||
file_root = pathlib.Path(root).joinpath(sourced_layer.subfolder if sourced_layer.subfolder else "")
|
||||
for tcl_file in sourced_layer.files:
|
||||
filepath = pathlib.Path(file_root).joinpath(f"{tcl_file}.tcl")
|
||||
if not filepath.exists():
|
||||
continue
|
||||
completion.reset()
|
||||
document = LSP_SERVER.workspace.get_text_document(filepath.as_uri()) # filepath.read_text(encoding="utf-8")
|
||||
tree = LSP_SERVER.parser.parse(document.source)
|
||||
tree.accept(completion, recurse=True)
|
||||
remove_existing_items(completion.custom_functions, LSP_SERVER.poco_completion)
|
||||
LSP_SERVER.poco_completion[str(filepath)] = completion.custom_functions
|
||||
remove_shared_keys(LSP_SERVER.proc_signatures, completion.proc_signatures)
|
||||
LSP_SERVER.proc_signatures[str(filepath)] = completion.proc_signatures
|
||||
except Exception as e:
|
||||
log_to_output(f"Fehler beim Parsen von {filepath}: {e}")
|
||||
|
||||
|
||||
@LSP_SERVER.feature(lsp.EXIT)
|
||||
def on_exit(_params: Optional[Any] = None) -> None:
|
||||
"""Handle clean up on exit."""
|
||||
@@ -433,6 +368,7 @@ def _get_global_defaults():
|
||||
"importStrategy": GLOBAL_SETTINGS.get("importStrategy", "useBundled"),
|
||||
"showNotifications": GLOBAL_SETTINGS.get("showNotifications", "off"),
|
||||
"formatter": GLOBAL_SETTINGS.get("formatter", True),
|
||||
"inlayHint": GLOBAL_SETTINGS.get("inlayHint", True),
|
||||
}
|
||||
|
||||
|
||||
@@ -504,9 +440,7 @@ def _get_settings_by_document(document: workspace.Document | None):
|
||||
# *****************************************************
|
||||
# Logging and notification.
|
||||
# *****************************************************
|
||||
def log_to_output(
|
||||
message: str, msg_type: lsp.MessageType = lsp.MessageType.Log
|
||||
) -> None:
|
||||
def log_to_output(message: str, msg_type: lsp.MessageType = lsp.MessageType.Log) -> None:
|
||||
LSP_SERVER.show_message_log(message, msg_type)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import logging
|
||||
import pathlib
|
||||
from typing import List, Optional, Tuple
|
||||
import lsprotocol.types as lsp
|
||||
from pygls.workspace.text_document import TextDocument
|
||||
from tclint.lexer import TclSyntaxError
|
||||
from tclint.format import FormatterOpts
|
||||
from tools.formatter import NxFormatter as Formatter
|
||||
from tclint.violations import Violation
|
||||
from plugins.poco_plugin import commands
|
||||
from tools import checks, parser
|
||||
from pygls import server, uris
|
||||
from tools.completion_items import completion, remove_existing_items, remove_shared_keys
|
||||
|
||||
|
||||
DIAGNOSTIC_SOURCE = "nx-post-support"
|
||||
|
||||
|
||||
class TclLanguageServer(server.LanguageServer):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.parser = parser.CustomParser()
|
||||
for command in commands:
|
||||
self.parser._commands.update(command)
|
||||
self.diagnostics = {}
|
||||
self.poco_completion: dict = {}
|
||||
self.proc_signatures: dict = {}
|
||||
|
||||
def update_poco_completion_for_file(self, document: TextDocument):
|
||||
"""Update poco_completion for a specific file when it changes"""
|
||||
filepath = str(pathlib.Path(uris.to_fs_path(document.uri)))
|
||||
|
||||
# Remove existing completion items for this file
|
||||
if filepath in self.poco_completion:
|
||||
del self.poco_completion[filepath]
|
||||
if filepath in self.proc_signatures:
|
||||
del self.proc_signatures[filepath]
|
||||
|
||||
# Parse and extract new completion items
|
||||
completion.reset()
|
||||
try:
|
||||
tree = self.parser.parse(document.source)
|
||||
tree.accept(completion, recurse=True)
|
||||
remove_existing_items(completion.custom_functions, self.poco_completion)
|
||||
self.poco_completion[filepath] = completion.custom_functions
|
||||
remove_shared_keys(self.proc_signatures, completion.proc_signatures)
|
||||
self.proc_signatures[filepath] = completion.proc_signatures
|
||||
except Exception as e:
|
||||
logging.debug(f"Error parsing {filepath}: {e}")
|
||||
|
||||
def format(
|
||||
self,
|
||||
document: TextDocument,
|
||||
options: lsp.FormattingOptions,
|
||||
range: Optional[Tuple[int, int]] = None,
|
||||
):
|
||||
# parser = Parser(command_plugins=["nx_plugins.poco_plugin.py"])
|
||||
# parser._commands.update(commands)
|
||||
|
||||
indent = "\t" if not options.insert_spaces else " " * options.tab_size
|
||||
formatter = Formatter(
|
||||
FormatterOpts(
|
||||
indent=indent,
|
||||
spaces_in_braces=False,
|
||||
max_blank_lines=500,
|
||||
indent_namespace_eval=True,
|
||||
),
|
||||
)
|
||||
|
||||
if range is not None:
|
||||
start, end = range
|
||||
return formatter.format_partial(document.source[start:end], self.parser)
|
||||
|
||||
return formatter.format_top(document.source, self.parser)
|
||||
|
||||
def linter(
|
||||
self,
|
||||
document: TextDocument,
|
||||
) -> List[Violation]:
|
||||
violations = []
|
||||
self.parser.violations = []
|
||||
tree = self.parser.parse(document.source)
|
||||
violations += self.parser.violations
|
||||
for checker in checks.get_checkers():
|
||||
violations += checker.check(document.source, tree)
|
||||
return violations
|
||||
|
||||
def lint(self, document: TextDocument):
|
||||
diagnostics = []
|
||||
|
||||
try:
|
||||
violations = self.linter(document)
|
||||
except TclSyntaxError as e:
|
||||
return [
|
||||
lsp.Diagnostic(
|
||||
message=str(e),
|
||||
severity=lsp.DiagnosticSeverity.Error,
|
||||
range=lsp.Range(
|
||||
start=lsp.Position(e.start[0] - 1, e.start[1] - 1),
|
||||
end=lsp.Position(e.end[0] - 1, e.end[1] - 1),
|
||||
),
|
||||
code="syntax error",
|
||||
source=DIAGNOSTIC_SOURCE,
|
||||
)
|
||||
]
|
||||
|
||||
for violation in violations:
|
||||
message = violation.message
|
||||
severity = lsp.DiagnosticSeverity.Warning
|
||||
start = lsp.Position(line=violation.start[0] - 1, character=violation.start[1] - 1)
|
||||
end = lsp.Position(line=violation.end[0] - 1, character=violation.end[1] - 1)
|
||||
|
||||
diagnostics.append(
|
||||
lsp.Diagnostic(
|
||||
message=message,
|
||||
severity=severity,
|
||||
range=lsp.Range(
|
||||
start=start,
|
||||
end=end,
|
||||
),
|
||||
code=violation.id,
|
||||
source=DIAGNOSTIC_SOURCE,
|
||||
)
|
||||
)
|
||||
|
||||
return diagnostics
|
||||
|
||||
def _compute_diagnostics(self, document: TextDocument) -> List[lsp.Diagnostic]:
|
||||
return self.lint(document)
|
||||
|
||||
def compute_diagnostics(self, document: TextDocument):
|
||||
# `None` sentinel ensures that `diagnostics` gets updated if the URI is not
|
||||
# present.
|
||||
_, previous = self.diagnostics.get(document, (0, None))
|
||||
|
||||
diagnostics = self._compute_diagnostics(document)
|
||||
|
||||
# Only update if the list has changed
|
||||
if previous != diagnostics:
|
||||
self.diagnostics[document.uri] = (document.version, diagnostics)
|
||||
@@ -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},
|
||||
]
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
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():
|
||||
parser = tcLintParser(True)
|
||||
tree = parser.parse("""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""")
|
||||
|
||||
print(tree.pretty(2))
|
||||
|
||||
|
||||
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""")
|
||||
|
||||
# print("Lexing input:\n", code)
|
||||
# print("\nTokens:\n" + "-" * 30)
|
||||
|
||||
while lexer.type() is not None:
|
||||
tok_type = lexer.type()
|
||||
tok_value = lexer.value()
|
||||
tok_pos = lexer.pos()
|
||||
print(f"Type: {tok_type:20} | Value: {repr(tok_value):20} | Pos: {tok_pos}")
|
||||
lexer.next()
|
||||
|
||||
|
||||
def test_1():
|
||||
code = """
|
||||
proc myProc {arg } {
|
||||
set myVar 1
|
||||
}
|
||||
|
||||
namespace eval myNS {
|
||||
proc innerProc {} {
|
||||
MOM_abort_program "Test"
|
||||
}
|
||||
}
|
||||
set result [myNS::innerProc]
|
||||
"""
|
||||
|
||||
lexer = Lexer()
|
||||
lexer.input(code)
|
||||
parser = Parser(lexer)
|
||||
ast = parser.parse()
|
||||
|
||||
visitor = NodeVisitor()
|
||||
ast.accept(visitor)
|
||||
|
||||
|
||||
class NodeVisitor:
|
||||
def visit_script(self, node):
|
||||
for stmt in node.statements:
|
||||
stmt.accept(self)
|
||||
|
||||
def visit_proc(self, node):
|
||||
print(f"Proc: {node.name}, args={node.args}")
|
||||
|
||||
def visit_set(self, node):
|
||||
print(f"Set: {node.varname} = {node.value}")
|
||||
|
||||
def visit_namespace(self, node):
|
||||
print(f"Namespace: {node.name}")
|
||||
|
||||
def visit_command(self, node):
|
||||
print(f"Command: {node.name} {node.args}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
from tclint.syntax_tree import Visitor, Command, BareWord, List
|
||||
import lsprotocol.types as lsp
|
||||
from common.load_data import standard_items
|
||||
|
||||
|
||||
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 not first_arg.value:
|
||||
return
|
||||
|
||||
if any(item.label == first_arg.value for item in standard_items.nx_procs):
|
||||
return
|
||||
|
||||
try:
|
||||
self._custom_functions.remove(first_arg.value)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
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
|
||||
|
||||
|
||||
def remove_existing_items(items: list[lsp.CompletionItem], store: dict) -> None:
|
||||
"""
|
||||
Entfernt alle CompletionItems aus dem store, deren label in der items-Liste vorkommt.
|
||||
Änderungen erfolgen in-place.
|
||||
"""
|
||||
labels_to_remove = {item.label for item in items}
|
||||
|
||||
for key in list(store.keys()):
|
||||
filtered = [ci for ci in store[key] if ci.label not in labels_to_remove]
|
||||
if filtered:
|
||||
store[key] = filtered
|
||||
else:
|
||||
del store[key]
|
||||
|
||||
|
||||
def remove_shared_keys(nested_dict: dict[str, dict[str, list]], flat_dict: dict[str, list]) -> None:
|
||||
"""
|
||||
Entfernt alle Keys aus nested_dict[file][func], wenn func auch in flat_dict vorhanden ist.
|
||||
Änderungen erfolgen in-place.
|
||||
"""
|
||||
for file_path, func_dict in list(nested_dict.items()):
|
||||
for func_name in list(func_dict.keys()):
|
||||
if func_name in flat_dict:
|
||||
del nested_dict[file_path][func_name]
|
||||
|
||||
if not nested_dict[file_path]:
|
||||
del nested_dict[file_path]
|
||||
|
||||
|
||||
completion = _Completion()
|
||||
@@ -0,0 +1,51 @@
|
||||
import xml.etree.ElementTree as ET
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Optional
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass
|
||||
class SourcedFile:
|
||||
layer_name: str
|
||||
subfolder: Optional[str]
|
||||
files: List[str]
|
||||
|
||||
|
||||
def read_psc_file(psc_file: Path) -> List[SourcedFile]:
|
||||
tree = ET.parse(psc_file)
|
||||
root = tree.getroot()
|
||||
|
||||
layers = root.findall(".//Layer")
|
||||
|
||||
layer_info_list: List[SourcedFile] = []
|
||||
for layer in layers:
|
||||
layer_name = layer.attrib.get("Name")
|
||||
subfolder = layer.attrib.get("SubFolder")
|
||||
# Scripts-Filenames
|
||||
scripts = layer.find("Scripts")
|
||||
script_names = []
|
||||
if scripts is not None:
|
||||
for filename in scripts.findall("Filename"):
|
||||
name = filename.attrib.get("Name")
|
||||
if name:
|
||||
script_names.append(name)
|
||||
|
||||
layer_info_list.append(
|
||||
SourcedFile(layer_name=layer_name, subfolder=subfolder, files=script_names)
|
||||
)
|
||||
return layer_info_list
|
||||
|
||||
|
||||
def get_all_psc_files(root_path: Path) -> list[Path]:
|
||||
return [path for path in root_path.rglob("*.psc")]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test = get_all_psc_files(
|
||||
Path(
|
||||
r"H:\janus-engineering-customers\KSB_Frankenthal\custom\library\machine\installed_machines\ksb_pe_grob_g550_sone\postprocessor"
|
||||
)
|
||||
)
|
||||
print(test)
|
||||
for pp in test:
|
||||
read_psc_file(pp)
|
||||
@@ -0,0 +1,31 @@
|
||||
from tclint.format import Formatter as BaseFormatter
|
||||
from typing import List
|
||||
|
||||
|
||||
class NxFormatter(BaseFormatter):
|
||||
"""
|
||||
Custom formatter that inherits from tclint's Formatter but preserves explicit
|
||||
line continuations (\\) inside braced expressions when they span multiple lines.
|
||||
|
||||
This avoids generating syntax errors in environments that require a trailing
|
||||
backslash for multi-line expressions (e.g., certain NX Post interpreters),
|
||||
while leaving all other formatting behavior unchanged.
|
||||
"""
|
||||
|
||||
def format_braced_expression(self, expr) -> List[str]: # type: ignore[override]
|
||||
# This method mirrors BaseFormatter.format_braced_expression but inserts
|
||||
# a line continuation (" \") between continuation lines similar to
|
||||
# BaseFormatter.format_expression.
|
||||
formatted = [""]
|
||||
for child in expr.children:
|
||||
lines = self.format(child)
|
||||
formatted[-1] += lines[0]
|
||||
for line in lines[1:]:
|
||||
# add continuation on the previous line; keep next line at the same level
|
||||
formatted[-1] += " \\" # keep explicit continuation
|
||||
formatted += [line]
|
||||
|
||||
if expr.pos[0] == expr.end_pos[0]:
|
||||
return self._brace(formatted)
|
||||
|
||||
return ["{"] + self._indent(formatted, self.opts.indent) + ["}"]
|
||||
|
||||
@@ -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
@@ -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())
|
||||
|
||||
@@ -1,76 +1,192 @@
|
||||
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
|
||||
import lsprotocol.types as lsp
|
||||
|
||||
|
||||
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, custom_functions: dict[str : list[lsp.CompletionItem]]):
|
||||
self._commands = get_commands(plugins)
|
||||
self._tokens = []
|
||||
self.custom_functions = custom_functions
|
||||
|
||||
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")
|
||||
name = word.value
|
||||
|
||||
def visit_var_sub(self, var_sub: VarSub):
|
||||
self._add_token(var_sub, "variable")
|
||||
in_standard = any(item.label == name for item in standard_items.nx_procs)
|
||||
|
||||
def visit_function(self, function: Function):
|
||||
self._add_token(function.name, "function")
|
||||
for arg in function.args:
|
||||
arg.accept(self, recurse=True)
|
||||
in_custom = any(item.label == name for items in self.custom_functions.values() for item in items)
|
||||
|
||||
if in_standard or in_custom:
|
||||
line, col = word.pos
|
||||
self._tokens.append((((line - 1, col - 1), len(name), "function", [])))
|
||||
|
||||
def collect_semantic_tokens(code: str):
|
||||
parser = Parser()
|
||||
tree = parser.parse(code)
|
||||
visitor = SemanticTokenCollector()
|
||||
tree.accept(visitor, recurse=True)
|
||||
return visitor.tokens
|
||||
def visit_command(self, command: Command):
|
||||
routine = command.routine
|
||||
|
||||
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],
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
def encode_tokens(tokens):
|
||||
tokens.sort()
|
||||
encoded = []
|
||||
if len(command.args) >= 2:
|
||||
param_list = command.args[1]
|
||||
|
||||
last_line = 0
|
||||
last_char = 0
|
||||
# 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],
|
||||
)
|
||||
)
|
||||
|
||||
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
|
||||
# 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", [])))
|
||||
|
||||
encoded.extend([delta_line, delta_start, length, token_type, modifiers])
|
||||
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 = line
|
||||
last_char = char if delta_line == 0 else 0
|
||||
tokens.append(Token(line_delta, col_delta, length, tok_type, tok_modifier))
|
||||
last_line, last_col = line, col
|
||||
|
||||
return encoded
|
||||
return tokens
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from typing import List, DefaultDict, Union
|
||||
|
||||
from tclint.syntax_tree import Visitor, Command, CommandSub, Node, Script
|
||||
|
||||
|
||||
class SymbolTable:
|
||||
"""Holds a symbol table (links symbols to nodes)."""
|
||||
|
||||
def __init__(self):
|
||||
self.proc_def: DefaultDict[str, list[Node]] = defaultdict(list)
|
||||
|
||||
def add_proc_definition(self, command: Command) -> None:
|
||||
"""Add definition of procedure"""
|
||||
# command holds the "proc" keyword, so the proc name is 1st argument
|
||||
proc_name_node = command.args[0]
|
||||
proc_name = proc_name_node.contents
|
||||
if not proc_name:
|
||||
return
|
||||
logging.debug(
|
||||
f"Definition of proc '{proc_name}' at {proc_name_node._pos_str()}"
|
||||
)
|
||||
self.proc_def[proc_name].append(proc_name_node)
|
||||
|
||||
def lookup_proc_definitions(self, symbol_text: str) -> List[Node]:
|
||||
"""Lookup definitions of the procedure pointed at by node"""
|
||||
if symbol_text is None or symbol_text not in self.proc_def:
|
||||
return []
|
||||
return self.proc_def[symbol_text]
|
||||
|
||||
|
||||
class SymbolTableBuilder(Visitor):
|
||||
"""Builds a symbol table."""
|
||||
|
||||
def __init__(self):
|
||||
self.table = SymbolTable()
|
||||
|
||||
def build(self, tree: Union[CommandSub, Script]) -> SymbolTable:
|
||||
"""Run the builder visitor through the syntax tree, building a table."""
|
||||
tree.accept(self, recurse=True)
|
||||
return self.table
|
||||
|
||||
def visit_command(self, command: Command) -> None:
|
||||
if command.routine.contents == "proc":
|
||||
self.table.add_proc_definition(command)
|
||||
+59
-214
@@ -1,30 +1,19 @@
|
||||
{
|
||||
"fileTypes": [
|
||||
"tcl"
|
||||
],
|
||||
"fileTypes": [ "tcl" ],
|
||||
"name": "tcl",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#main"
|
||||
}
|
||||
{ "include": "#main" }
|
||||
],
|
||||
"scopeName": "source.tcl",
|
||||
"uuid": "c7017136-2ff2-48e9-bdb0-570cf238b4a2",
|
||||
"repository": {
|
||||
"main": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#command"
|
||||
}
|
||||
]
|
||||
"patterns": [ { "include": "#command" } ]
|
||||
},
|
||||
"args": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#quoted_args"
|
||||
},
|
||||
{
|
||||
"include": "#numeric"
|
||||
},
|
||||
{ "include": "#quoted_args" },
|
||||
{ "include": "#numeric" },
|
||||
{
|
||||
"match": "((?<=;)\\s*#.*$)",
|
||||
"name": "comment.tcl",
|
||||
@@ -36,22 +25,10 @@
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "((?<!\\\\)\\{)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.tcl"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#braced__1"
|
||||
}
|
||||
],
|
||||
"end": "(\\}([^\\s\\]]*))",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.tcl"
|
||||
}
|
||||
}
|
||||
"beginCaptures": { "1": { "name": "punctuation.tcl" } },
|
||||
"patterns": [ { "include": "#braced__1" } ],
|
||||
"end": "(\\}([^\\s\\]\\x{0022}]*))",
|
||||
"endCaptures": { "1": { "name": "punctuation.tcl" } }
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -61,42 +38,24 @@
|
||||
"match": "(\\\\(?:\\d{1,3}|x[a-fA-F0-9]{1,2}|u[a-fA-F0-9]{1,4}|U[a-fA-F0-9]{1,8}|.))",
|
||||
"name": "constant.character.escape.tcl"
|
||||
},
|
||||
{
|
||||
"include": "#keywords"
|
||||
},
|
||||
{ "include": "#keywords" },
|
||||
{
|
||||
"match": "((?<={)\\s*(?:after|append|apply|array|auto_execok|auto_import|auto_load|auto_mkindex|auto_qualify|auto_reset|bgerror|binary|break|catch|cd|chan|clock|close|concat|continue|coroutine|dde|dict|encoding|eof|error|eval|exec|exit|expr|fblocked|fconfigure|fcopy|fileevent|file|flush|foreach|for|format|gets|global|glob|history|http|if|incr|info|interp|join|lappend|lassign|lindex|linsert|list|llength|lmap|load|lrange|lrepeat|lreplace|lreverse|lsearch|lset|lsort|memory|my|namespace|nextto|next|oo::class|oo::copy|oo::define|oo::objdefine|oo::object|open|package|parray|pid|pkg::create|pkg_mkIndex|platform::shell|proc|puts|pwd|read|regexp|registry|regsub|rename|return|scan|seek|self|set|socket|source|split|string|subst|switch|tailcall|tcl::prefix|tcl_endOfWord|tcl_findLibrary|tcl_startOfNextWord|tcl_startOfPreviousWord|tcl_wordBreakAfter|tcl_wordBreakBefore|tell|throw|time|trace|try|unknown|unload|unset|update|uplevel|upvar|variable|vwait|while|yieldto|yield)\\s+)",
|
||||
"name": "keyword.tcl",
|
||||
"comment": "Special handling for known commands."
|
||||
},
|
||||
{
|
||||
"include": "#braced_inner"
|
||||
},
|
||||
{
|
||||
"include": "#args"
|
||||
}
|
||||
{ "include": "#braced_inner" },
|
||||
{ "include": "#args" }
|
||||
]
|
||||
},
|
||||
"braced_inner": {
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "(\\{)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.tcl"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#braced_inner__1"
|
||||
}
|
||||
],
|
||||
"beginCaptures": { "1": { "name": "punctuation.tcl" } },
|
||||
"patterns": [ { "include": "#braced_inner__1" } ],
|
||||
"end": "(\\})",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.tcl"
|
||||
}
|
||||
}
|
||||
"endCaptures": { "1": { "name": "punctuation.tcl" } }
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -106,15 +65,9 @@
|
||||
"match": "(\\\\[\\x{007b}\\x{007d}])",
|
||||
"name": "constant.character.escape.tcl"
|
||||
},
|
||||
{
|
||||
"include": "#numeric"
|
||||
},
|
||||
{
|
||||
"include": "#braced_inner"
|
||||
},
|
||||
{
|
||||
"include": "#args"
|
||||
}
|
||||
{ "include": "#numeric" },
|
||||
{ "include": "#braced_inner" },
|
||||
{ "include": "#args" }
|
||||
]
|
||||
},
|
||||
"braced_lit": {
|
||||
@@ -139,22 +92,10 @@
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "(\\\\\\s*$)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "constant.character.escape.tcl"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#command__1"
|
||||
}
|
||||
],
|
||||
"beginCaptures": { "1": { "name": "constant.character.escape.tcl" } },
|
||||
"patterns": [ { "include": "#command__1" } ],
|
||||
"end": "((?<!\\\\\\s)$)",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "none.tcl"
|
||||
}
|
||||
}
|
||||
"endCaptures": { "1": { "name": "none.tcl" } }
|
||||
},
|
||||
{
|
||||
"match": "(^\\s*#.*)",
|
||||
@@ -169,73 +110,41 @@
|
||||
{
|
||||
"match": "(?<=^|{)(\\s*proc\\s+)(\\S+\\s+)((?:(?<!\\\\){)(?:[^\\x{007b}\\x{007d}\\n]*(?:\\g<1>(?:\\\\{|\\\\}|[^\\x{007b}\\x{007d}])*)*)})",
|
||||
"captures": {
|
||||
"1": {
|
||||
"name": "keyword.tcl"
|
||||
},
|
||||
"2": {
|
||||
"name": "entity.name.function.tcl"
|
||||
},
|
||||
"3": {
|
||||
"name": "none.tcl"
|
||||
}
|
||||
"1": { "name": "keyword.tcl" },
|
||||
"2": { "name": "entity.name.function.tcl" },
|
||||
"3": { "name": "none.tcl" }
|
||||
},
|
||||
"comment": "Proc command."
|
||||
},
|
||||
{ "include": "#regexp" },
|
||||
{ "include": "#keywords" },
|
||||
{
|
||||
"include": "#regexp"
|
||||
},
|
||||
{
|
||||
"include": "#keywords"
|
||||
},
|
||||
{
|
||||
"match": "(^\\s*\\S+|(?:(?<=[^\\x{005c}]\\[)[^\\s\\]]++(?!\\])))",
|
||||
"match": "(^\\s*[^\\s\\x{0022}]+|(?:(?<=[^\\x{005c}]\\[)[^\\s\\]]+))",
|
||||
"name": "keyword.tcl",
|
||||
"comment": "All other commands. (NOTE: Iro doesn't support possessive quantifiers, but the grammar does; be sure to replace the second + with ++ after regenerating!)"
|
||||
},
|
||||
{
|
||||
"include": "#args"
|
||||
}
|
||||
{ "include": "#args" }
|
||||
]
|
||||
},
|
||||
"command__1": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#keywords"
|
||||
},
|
||||
{
|
||||
"include": "#args"
|
||||
}
|
||||
{ "include": "#keywords" },
|
||||
{ "include": "#args" }
|
||||
]
|
||||
},
|
||||
"embedded": {
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "((?<!\\\\)\\[)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.tcl"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#embedded__1"
|
||||
}
|
||||
],
|
||||
"beginCaptures": { "1": { "name": "punctuation.tcl" } },
|
||||
"patterns": [ { "include": "#embedded__1" } ],
|
||||
"end": "((?<!\\\\)\\])",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.tcl"
|
||||
}
|
||||
}
|
||||
"endCaptures": { "1": { "name": "punctuation.tcl" } }
|
||||
}
|
||||
]
|
||||
},
|
||||
"embedded__1": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#command"
|
||||
}
|
||||
]
|
||||
"patterns": [ { "include": "#command" } ]
|
||||
},
|
||||
"keywords": {
|
||||
"patterns": [
|
||||
@@ -263,30 +172,16 @@
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "((?<!\\\\)\")",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "string.tcl"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#quoted__1"
|
||||
}
|
||||
],
|
||||
"beginCaptures": { "1": { "name": "string.tcl" } },
|
||||
"patterns": [ { "include": "#quoted__1" } ],
|
||||
"end": "(\")",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "string.tcl"
|
||||
}
|
||||
}
|
||||
"endCaptures": { "1": { "name": "string.tcl" } }
|
||||
}
|
||||
]
|
||||
},
|
||||
"quoted__1": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#quoted_args"
|
||||
},
|
||||
{ "include": "#quoted_args" },
|
||||
{
|
||||
"match": "([^\\x{0024}\\x{005b}\\x{0022}\\x{005c}]+)",
|
||||
"name": "string.tcl"
|
||||
@@ -295,22 +190,14 @@
|
||||
},
|
||||
"quoted_args": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#braced"
|
||||
},
|
||||
{
|
||||
"include": "#quoted"
|
||||
},
|
||||
{
|
||||
"include": "#embedded"
|
||||
},
|
||||
{ "include": "#braced" },
|
||||
{ "include": "#quoted" },
|
||||
{ "include": "#embedded" },
|
||||
{
|
||||
"match": "(\\\\(?:\\d{1,3}|x[a-fA-F0-9]{1,2}|u[a-fA-F0-9]{1,4}|U[a-fA-F0-9]{1,8}|.))",
|
||||
"name": "constant.character.escape.tcl"
|
||||
},
|
||||
{
|
||||
"include": "#variable"
|
||||
}
|
||||
{ "include": "#variable" }
|
||||
]
|
||||
},
|
||||
"regexp": {
|
||||
@@ -318,57 +205,27 @@
|
||||
{
|
||||
"begin": "((?<=^|[\\[\\x{007b}\\x{003b}])\\s*(?:regexp|regsub)\\s+)((?:(?:(?:-about|-all|-expanded|-indices|-inline|-lineanchor|-linestop|-line|-nocase|(?:-index\\s+(?:end|\\d+)(?:[\\+\\-]-?\\d+)?))\\s+)*(?:--\\s+)?)?)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "keyword.tcl"
|
||||
},
|
||||
"2": {
|
||||
"name": "none.tcl"
|
||||
}
|
||||
"1": { "name": "keyword.tcl" },
|
||||
"2": { "name": "none.tcl" }
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#regexp__1"
|
||||
}
|
||||
],
|
||||
"patterns": [ { "include": "#regexp__1" } ],
|
||||
"end": "(\\s+)",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "none.tcl"
|
||||
}
|
||||
}
|
||||
"endCaptures": { "1": { "name": "none.tcl" } }
|
||||
},
|
||||
{
|
||||
"include": "#args"
|
||||
}
|
||||
{ "include": "#args" }
|
||||
]
|
||||
},
|
||||
"regexp__1": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#braced_lit_re"
|
||||
},
|
||||
{ "include": "#braced_lit_re" },
|
||||
{
|
||||
"begin": "((?:(?<!\\\\)\"))",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "string.regexp.tcl"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#regexp__2"
|
||||
}
|
||||
],
|
||||
"beginCaptures": { "1": { "name": "string.regexp.tcl" } },
|
||||
"patterns": [ { "include": "#regexp__2" } ],
|
||||
"end": "((?:(?<!\\\\)\"))",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "string.regexp.tcl"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"include": "#variable"
|
||||
"endCaptures": { "1": { "name": "string.regexp.tcl" } }
|
||||
},
|
||||
{ "include": "#variable" },
|
||||
{
|
||||
"match": "(\\S+)",
|
||||
"name": "string.regexp.tcl",
|
||||
@@ -378,15 +235,9 @@
|
||||
},
|
||||
"regexp__2": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#braced_lit_re"
|
||||
},
|
||||
{
|
||||
"include": "#embedded"
|
||||
},
|
||||
{
|
||||
"include": "#variable"
|
||||
},
|
||||
{ "include": "#braced_lit_re" },
|
||||
{ "include": "#embedded" },
|
||||
{ "include": "#variable" },
|
||||
{
|
||||
"match": "(\\\\\"|[^\\x{0022}])",
|
||||
"name": "string.regexp.tcl",
|
||||
@@ -404,15 +255,9 @@
|
||||
},
|
||||
"word_lit__1": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#braced_lit"
|
||||
},
|
||||
{
|
||||
"include": "#embedded"
|
||||
},
|
||||
{
|
||||
"include": "#variable"
|
||||
},
|
||||
{ "include": "#braced_lit" },
|
||||
{ "include": "#embedded" },
|
||||
{ "include": "#variable" },
|
||||
{
|
||||
"match": "(\\\\\"|[^\\x{0022}])",
|
||||
"name": "string.tcl",
|
||||
|
||||
+110
-7
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user