Compare commits

...
19 Commits
Author SHA1 Message Date
Christoph f78b4836f9 Merge pull request 'tcl_language_support' (#16) from tcl_language_support into main
/ build_and_publish (push) Successful in 30s
Reviewed-on: #16
2025-07-21 20:21:36 +00:00
Christoph 3fbe4f9243 add more functions 2025-07-21 22:19:49 +02:00
Christoph 5a020f7d07 Merge branch 'tcl_language_support' of https://git.cbsk-tech.de/Christoph/nx_post_support into tcl_language_support 2025-07-21 21:31:06 +02:00
Christoph cf6a39b51d add hover function 2025-07-21 21:31:04 +02:00
Christoph Brandau b264f20baf update settings 2025-07-21 16:20:16 +02:00
Christoph 4ef697190a Update version to 0.4.7
/ build_and_publish (push) Has been cancelled
2025-07-21 07:13:58 +00:00
Christoph 896e9f6c1f Merge pull request 'tcl_language_support' (#15) from tcl_language_support into main
/ build_and_publish (push) Successful in 39s
Reviewed-on: #15
2025-07-21 07:13:14 +00:00
Christoph Brandau 52a2b2f774 fix formatter 2025-07-21 09:12:36 +02:00
Christoph Brandau ee5240c861 add formatter settings 2025-07-21 09:08:53 +02:00
Christoph 61fb0bc5d2 Update version to 0.4.6 2025-07-21 05:39:02 +00:00
Christoph 95ddae6d13 Merge pull request 'fix buffer @ in formatter' (#14) from tcl_language_support into main
/ build_and_publish (push) Successful in 45s
Reviewed-on: #14
2025-07-21 05:38:09 +00:00
Christoph Brandau a6f616a89d fix buffer @ in formatter 2025-07-21 07:37:31 +02:00
Christoph 632fc32c3e Update version to 0.4.5 2025-07-21 05:16:43 +00:00
Christoph 9c5b76510d Merge pull request 'fix formatter for buffers' (#13) from tcl_language_support into main
/ build_and_publish (push) Successful in 27s
Reviewed-on: #13
2025-07-21 05:15:49 +00:00
Christoph Brandau 1b6b25c137 fix formatter for buffers 2025-07-21 07:15:27 +02:00
Christoph e84a08e22b Update version to 0.4.4 2025-07-21 04:53:41 +00:00
Christoph 1070429039 Merge pull request 'fix the formatter' (#12) from tcl_language_support into main
/ build_and_publish (push) Successful in 47s
Reviewed-on: #12
2025-07-21 04:52:40 +00:00
Christoph Brandau b17a4f8ba0 fix the formatter 2025-07-21 06:52:20 +02:00
Christoph 65732ffac8 Update version to 0.4.3 2025-07-21 04:42:13 +00:00
7 changed files with 343 additions and 89 deletions
+6 -3
View File
@@ -78,7 +78,8 @@ export async function getWorkspaceSettings(
path: resolveVariables(config.get<string[]>(`path`) ?? [], workspace),
interpreter: resolveVariables(interpreter, workspace),
importStrategy: config.get<string>(`importStrategy`) ?? "useBundled",
showNotifications: config.get<string>(`showNotifications`) ?? "off"
showNotifications: config.get<string>(`showNotifications`) ?? "off",
formatter: config.get<boolean>(`formatter`) ?? true
}
return workspaceSetting
}
@@ -109,7 +110,8 @@ export async function getGlobalSettings(
path: getGlobalValue<string[]>(config, "path", []),
interpreter: interpreter,
importStrategy: getGlobalValue<string>(config, "importStrategy", "useBundled"),
showNotifications: getGlobalValue<string>(config, "showNotifications", "off")
showNotifications: getGlobalValue<string>(config, "showNotifications", "off"),
formatter: config.get<boolean>(`formatter`) ?? true
}
return setting
}
@@ -123,7 +125,8 @@ export function checkIfConfigurationChanged(
`${namespace}.path`,
`${namespace}.interpreter`,
`${namespace}.importStrategy`,
`${namespace}.showNotifications`
`${namespace}.showNotifications`,
`${namespace}.formatter`
]
const changed = settings.map((s) => e.affectsConfiguration(s))
return changed.includes(true)
+6 -1
View File
@@ -2,7 +2,7 @@
"name": "nx-post-support",
"displayName": "NX Postprocessor Support",
"description": "",
"version": "0.4.2",
"version": "0.4.7",
"publisher": "Christoph",
"serverInfo": {
"name": "NX Postprocessor Support",
@@ -79,6 +79,11 @@
],
"configuration": {
"properties": {
"nx-post-support.formatter": {
"type": "boolean",
"default": false,
"description": "Use the TCL formatter from `NX Postprocessor Support`"
},
"nx-post-support.importStrategy": {
"default": "useBundled",
"description": "Defines where `NX Postprocessor Support` is imported from.",
+226
View File
@@ -4,6 +4,22 @@
"label": "proc",
"kind": "keyword"
},
{
"label": "array",
"kind": "keyword"
},
{
"label": "lappend",
"kind": "keyword"
},
{
"label": "string",
"kind": "function"
},
{
"label": "switch",
"kind": "keyword"
},
{
"label": "if",
"kind": "keyword"
@@ -19,6 +35,14 @@
{
"label": "puts",
"kind": "function"
},
{
"label": "set",
"kind": "keyword"
},
{
"label": "namespace",
"kind": "keyword"
}
],
"MOM_procs": [
@@ -233,6 +257,164 @@
"set path [MOM_ask_env_var UGII_CAM_POST_CONFIG_FILE]"
]
},
{
"label": "MOM_ask_ess_exp_value",
"kind": "function",
"description": "This function provides access to the variables of the NX Expression module. The NX Expression module allows users to define variables and expressions to use as parameters for NX entities such as modeling features.",
"format": "MOM_ask_ess_exp_value <variable_name>",
"parameters": [
{
"name": "variable_name",
"desc": "Name of the NX Expression variable."
}
],
"returns": [
"Value of the NX Expression variable."
],
"example": [
"MOM_ask_ess_exp_value diameter"
]
},
{
"label": "MOM_ask_event_type",
"kind": "function",
"description": "This procedure returns the name of the current event. This is the last event that the event generator executed.",
"format": "MOM_ask_event_type",
"parameters": [],
"returns": [
"Name of current event."
],
"example": [
"set event [MOM_ask_event_type]"
]
},
{
"label": "MOM_ask_group_info",
"kind": "function",
"description": "This command retrieves group information in a CAM setup from the operation navigator views for the specified operation or group name. Returns an integer value (-1, 0, or 1) and stores the result in the variables mom_result and mom_result1.",
"format": "MOM_ask_group_info <node_name> <view> <parent|children>",
"parameters": [
{
"name": "node_name",
"desc": "Operation or group name."
},
{
"name": "view",
"desc": "View in the operation navigator (ONT). (program|prog, tool, geometry|geom, method|meth)"
},
{
"name": "parent|children",
"desc": "Type of node."
}
],
"returns": [
"-1: Query failed, or the node does not exist.",
"0: Current node is root node, or no child node.",
"1: Get current group information."
],
"example": [
"MOM_ask_group_info UGT0361_019 tool children",
"mom_result(0) = CHAMFER_S1H_CSINK",
"mom_result(1) = CHAMFER_S1H_CSINK1",
"mom_result1(CHAMFER_S1H_CSINK) = Drilling",
"mom_result1(CHAMFER_S1H_CSINK1) = Drilling",
"MOM_ask_group_info CHAMFER_S1H_CSINK tool parent",
"mom_result = UGT0361_019",
"mom_result1 = Countersink"
]
},
{
"label": "MOM_ask_init_junction_xform",
"kind": "function",
"description": "This command returns the transformation matrix of the junction at its initial state with respect to the absolute coordinate system (ACS). It passes the results to mom_sim_result (matrix: list of 9) and mom_sim_result1 (origin: list of 3).",
"format": "MOM_ask_init_junction_xform <junction_name>",
"parameters": [
{
"name": "junction_name",
"desc": "Name of junction."
}
],
"example": []
},
{
"label": "MOM_ask_library_attributes",
"kind": "function",
"description": "This function retrieves attributes for an item from the specified library.",
"format": "MOM_ask_library_attributes <library_key> <libref> <db_alias>+",
"parameters": [
{
"name": "library_key",
"desc": "The entry in the CAM configuration file for the desired library."
},
{
"name": "libref",
"desc": "Unique identifier of specified item in the library."
},
{
"name": "db_alias",
"desc": "The db_alias names are found in the definition file for that library. There can be a list of more than one name."
}
],
"returns": [
"The returned values are placed in the array mom_lib_attr_value."
],
"example": [
"global mom_lib_attr_value mom_libref",
"MOM_ask_library_attributes LIBRARY_TOOL $mom_libref Type SubType",
"MOM_output_literal \"Type = $mom_lib_attr_value(Type)\"",
"MOM_output_literal \"SubType = $mom_lib_attr_value(SubType)\""
]
},
{
"label": "MOM_ask_machine_zero_junction_name",
"kind": "function",
"description": "This command returns the name of the MACHINE_ZERO junction.",
"format": "MOM_ask_machine_zero_junction_name",
"parameters": [],
"returns": [
"Name of the MACHINE_ZERO junction."
],
"example": [
"set jct_name [MOM_ask_machine_zero_junction_name]"
]
},
{
"label": "MOM_ask_oper_csys",
"kind": "function",
"description": "Fetches the coordinate system (CSYS) information of the named operation. The command passes the results in three MOM variables mom_result, mom_result1, and mom_result2.",
"format": "MOM_ask_oper_csys <operation_name>",
"parameters": [
{
"name": "operation_name",
"desc": "Name of the operation."
}
],
"returns": [
"mom_result is the Special Purpose setting of the MCS.\n0 = None\n1 = Use Main MCS\n2 = Fixture Offset\n3 = CSYS Rotation",
"`mom_result1` is a list of 12 double precision values defining the mom_csys_matrix of the operation.",
"`mom_result2` is a list of 12 double precision values defining the `mom_machine_csys_matrix` of the operation."
],
"example": [
"set jct_name [MOM_ask_machine_zero_junction_name]"
]
},
{
"label": "MOM_ask_oper_info",
"kind": "function",
"description": "Retrieves operation information at the start of a program and stores the result in the array variable `mom_operation_info`. Called from Post Configurator procedure `MOM_start_of_path_LIB`.",
"format": "MOM_ask_oper_info <opr_list>",
"parameters": [
{
"name": "opr_list",
"desc": "List of operations."
}
],
"returns": [],
"example": [
"set opr_list \"PLANER_MILL FACE_MILLING1 GENERIC_MOTION1\"",
"MOM_ask_oper_info opr_list"
]
},
{
"label": "MOM_ask_ude_info",
"kind": "function",
@@ -257,6 +439,50 @@
"1 - The UDE information successfully retrieved."
],
"example": []
},
{
"label": "MOM_source",
"kind": "function",
"description": "Allows you to specify TCL files to be included as a source of commands and data during postprocessing.",
"format": "MOM_source <filename>+",
"parameters": [
{
"name": "filename(s)",
"desc": "Name of TCL file(s) to be included at runtime."
}
],
"example": [
"MOM_source my_header.tcl"
]
},
{
"label": "MOM_suppress",
"kind": "function",
"description": "The next time that a block template that contains a reference to any of the input address names is evaluated, the word that contains the address is not be output regardless of its modality attribute.",
"format": "MOM_suppress <Always|Once|Off> <address_1 ... address_n>",
"parameters": [
{
"name": "Always|Once|Off",
"desc": "Always - Sets the address to be to be non-modal (always output).\nOnce - The next time that a block template that contains a reference to any of the input address names is evaluated, the word that contains that address is not output regardless of its modality attribute. If both MOM_force Once and MOM_suppress Once are programmed, the last one will take effect. If MOM_suppress Always is programmed, the address is never output. All MOM_force Once and MOM_suppress Once commands are ignored.\nff - Sets the address to be to be modal (output only if value changes)."
},
{
"name": "address_1 ... address_n",
"desc": "Name of output address(es)."
}
],
"example": [
"MOM_suppress_address Always N X Y"
]
},
{
"label": "MOM_update_kinematics",
"kind": "function",
"description": "Maps the following legacy kinematics variables to the current kinematics variables, and is required after specifying them: mom_kin_4th_axis_center_offset, mom_kin_5th_axis_center_offset, mom_kin_pivot_gauge_offset.",
"format": "MOM_update_kinematics",
"parameters": [],
"example": [
"MOM_update_kinematics"
]
}
],
"mom_variables": [
+14 -9
View File
@@ -15,43 +15,48 @@ def format_tcl(src: str, indent_str=" ") -> str:
for raw_line in src.splitlines():
stripped = raw_line.strip()
# 1) Single-line 'if {cond} {action}' → no indent change
# 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
# 2) Combined '} else {' → dedent, print, then indent
# 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
# 3) SPECIAL: closing brace plus tag on same line: '} Tag'
m = re.match(r"^\}\s+(\w+)", stripped)
if m:
# 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
# 4) Pure '}' → dedent then print
# Pure '}' → dedent then print
if stripped == "}":
level = max(level - 1, 0)
out_lines.append(f"{indent_str * level}{stripped}")
continue
# 5) 'elseif' or 'else' alone → align with matching 'if'
# '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
# 6) Default: print at current indent
# Default: print at current indent
out_lines.append(f"{indent_str * level}{stripped}")
# 7) Open a new block on lines ending with '{'
# Open a new block on lines ending with '{'
if re.match(r"^(if|elseif)\b.*\{$", stripped) or stripped.endswith("{"):
level += 1
+12 -10
View File
@@ -17,10 +17,15 @@ KIND_MAP = {
class StandardCompletionItems:
def __init__(self):
self.__json_data: dict = self.__load_json()
self.__tcl_keyword_list = self.__load_tcl_keyword()
self.__nx_procs = self.__load_nx_procs()
self.__nx_variables = self.__load_nx_variables()
@property
def json_data(self):
return self.__json_data
@property
def tcl_keyword_list(self):
return self.__tcl_keyword_list
@@ -33,11 +38,15 @@ class StandardCompletionItems:
def nx_variables(self):
return self.__nx_variables
def __load_tcl_keyword(self) -> list[lsp.CompletionItem]:
def __load_json(self) -> dict:
with open(
pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r"
) as f:
data = json.load(f)
return data
def __load_tcl_keyword(self) -> list[lsp.CompletionItem]:
data = self.json_data
items = []
for i in data.get("tcl", []):
@@ -50,10 +59,7 @@ class StandardCompletionItems:
return items
def __load_nx_variables(self):
with open(
pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r"
) as f:
data = json.load(f)
data = self.json_data
items = []
for var in data.get("mom_variables", []):
label = var.get("label", "")
@@ -85,11 +91,7 @@ class StandardCompletionItems:
return items
def __load_nx_procs(self) -> list[lsp.CompletionItem]:
with open(
pathlib.Path(__file__).parent.joinpath("completion_list.json"), "r"
) as f:
data = json.load(f)
data = self.json_data
items = []
for proc in data.get("MOM_procs", []):
label = proc.get("label", "")
+79 -5
View File
@@ -13,6 +13,7 @@ import sys
import sysconfig
import traceback
from typing import Any, Optional, Sequence
import re
# **********************************************************
@@ -54,9 +55,6 @@ LSP_SERVER = server.LanguageServer(
name="NX Postprocessor Support", version="0.0.1", max_workers=MAX_WORKERS
)
NX_PROC_LIST = []
# **********************************************************
# Tool specific code goes below this.
# **********************************************************
@@ -106,6 +104,72 @@ def on_completion(params: lsp.CompletionParams) -> list[lsp.CompletionItem]:
return lsp.CompletionList(is_incomplete=False, items=items)
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_HOVER)
def hover(params: lsp.HoverParams) -> lsp.Hover:
pos = params.position
document_uri = params.text_document.uri
document = LSP_SERVER.workspace.get_text_document(document_uri)
col = params.position.character
try:
line = document.lines[pos.line]
except IndexError:
return None
for m in re.finditer(r"\b\w+\b", line):
if m.start() <= col <= m.end():
token = m.group(0)
break
else:
return None
command = token
data = standard_items.json_data
all_items = data.get("MOM_procs", []) + data.get("mom_variables", [])
match = next((item for item in all_items if item["label"] == command), None)
if not match:
return None
if not match.get("kind") == "function":
return None
label = match.get("label", "")
parameters = match.get("parameters", [])
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)
returns_data = match.get("returns", ["None"])
returns_md = "\n".join(f"- {line}" for line in returns_data)
doc_md = f"""\
### 📘 {label}
**Purpose**
{match.get("description", "No description available.")}
**Format**
`{match.get("format", label)}`
**Parameters**
{param_lines}
**Return value**
{returns_md}
**Example**
```tcl
{example_md}"""
return lsp.Hover(lsp.MarkupContent(kind=lsp.MarkupKind.Markdown, value=doc_md))
# **********************************************************
# Linting features end here
# **********************************************************
@@ -127,7 +191,9 @@ def formatting(params: lsp.DocumentFormattingParams) -> list[lsp.TextEdit] | Non
doc = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
text = doc.source
formatted = format_tcl(text)
formatted = text
if GLOBAL_SETTINGS.get("formatter", False):
formatted = format_tcl(text)
last_line = len(text.splitlines())
full_range = lsp.Range(
@@ -141,6 +207,13 @@ def formatting(params: lsp.DocumentFormattingParams) -> list[lsp.TextEdit] | Non
# **********************************************************
# Required Language Server Initialization and Exit handlers.
# **********************************************************
@LSP_SERVER.feature(lsp.WORKSPACE_DID_CHANGE_CONFIGURATION)
def did_change_configuration(params: lsp.DidChangeConfigurationParams):
settings = params.settings
log_to_output(str(settings))
@LSP_SERVER.feature(lsp.INITIALIZE)
def initialize(params: lsp.InitializeParams) -> lsp.InitializeResult:
"""LSP handler for initialize request."""
@@ -161,7 +234,7 @@ def initialize(params: lsp.InitializeParams) -> lsp.InitializeResult:
)
return lsp.InitializeResult(
capabilities=lsp.ServerCapabilities(
document_formatting_provider=True,
document_formatting_provider=GLOBAL_SETTINGS.get("formatter", True),
)
)
@@ -185,6 +258,7 @@ def _get_global_defaults():
"args": GLOBAL_SETTINGS.get("args", []),
"importStrategy": GLOBAL_SETTINGS.get("importStrategy", "useBundled"),
"showNotifications": GLOBAL_SETTINGS.get("showNotifications", "off"),
"formatter": GLOBAL_SETTINGS.get("formatter", True),
}
-61
View File
@@ -1,61 +0,0 @@
// Lark grammar for Tcl scripts with proc definitions and optional args
%import common.CNAME -> IDENTIFIER
%import common.INT -> INT
%import common.FLOAT -> FLOAT
%import common.ESCAPED_STRING -> STRING_LITERAL
%import common.WS_INLINE -> WS_INLINE
%import common.NEWLINE -> NEWLINE
// Ignore only spaces, tabs; treat newlines as separators
%ignore WS_INLINE
// Tcl comments
%ignore COMMENT
start: script
script: statement*
statement: command (";" | NEWLINE)?
command: set_command
| proc_command
| r_break
| r_continue
proc_command: "proc" IDENTIFIER "{" argument_list "}" body_block
argument_list: [argument (argument)*]
argument: IDENTIFIER -> required_arg
| "{" IDENTIFIER (value)? "}" -> optional_arg
value: STRING_LITERAL
| FLOAT
| INT
| IDENTIFIER
| list
list: LBRACE [value (WS_INLINE value)*] RBRACE
body_block: LBRACE body_content RBRACE -> body
body_content: ( /[^{}]+/ | body_block )* // allow nested braces
set_command: "set" IDENTIFIER index? value
// Index: parentheses with comma-separated items
index: "(" index_item ("," index_item)* ")"
index_item: INT -> index_int
| STRING_VAR -> index_string
| "$" IDENTIFIER index? -> nested_index
r_break: "break"
r_continue: "continue"
LBRACE: "{"
RBRACE: "}"
STRING_VAR: /(?:[^"\\]|\\.)+/
COMMENT: /#[^\r\n]*/