tcl_language_support #19

Merged
Christoph merged 9 commits from tcl_language_support into main 2025-08-03 19:07:19 +00:00
8 changed files with 374 additions and 305 deletions
Showing only changes of commit 0318532308 - Show all commits
+5 -5
View File
@@ -179,11 +179,11 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(formatDefProvider)
// const tclOutlineProvider = vscode.languages.registerDocumentSymbolProvider(
// { scheme: "file", language: "tcl" },
// { provideDocumentSymbols: tclDocumentSymbolProvider }
// )
// context.subscriptions.push(tclOutlineProvider)
const tclOutlineProvider = vscode.languages.registerDocumentSymbolProvider(
{ scheme: "file", language: "tcl" },
{ provideDocumentSymbols: tclDocumentSymbolProvider }
)
context.subscriptions.push(tclOutlineProvider)
// Diagnostics collection
const diagnosticCollectionCdl = vscode.languages.createDiagnosticCollection("cdl")
-63
View File
@@ -1,63 +0,0 @@
import re
def format_tcl(src: str, indent_str=" ") -> str:
"""
Simple Tcl formatter with special handling for:
1) Single-line 'if {cond} {action}' blocks remain on one line.
2) Combined closing-and-opening lines like '} else {' dedent then re-indent.
3) Lines like '} Tag' stay on the same line: '} Tag'.
4) Standard multi-line blocks for 'if', 'elseif', 'else', '{', '}'.
"""
level = 0
out_lines = []
for raw_line in src.splitlines():
stripped = raw_line.strip()
# Comments are ignored
if stripped.startswith("#"):
out_lines.append(indent_str * level + stripped)
continue
# Single-line 'if {cond} {action}' → no indent change
if re.match(r"^(if|elseif)\s*\{[^}]+\}\s*\{[^}]+\}$", stripped):
out_lines.append(indent_str * level + stripped)
continue
# Combined '} else {' → dedent, print, then indent
if re.match(r"^\}\s*(elseif|else)\b.*\{$", stripped):
level = max(level - 1, 0)
out_lines.append(indent_str * level + stripped)
level += 1
continue
# SPECIAL: closing brace plus tag on same line: '} Tag'
m = re.match(r"^\}\s+(.+)", stripped)
if m and not stripped.startswith("#"):
# close one block
level = max(level - 1, 0)
# stay on one line: "} Tag"
out_lines.append(f"{indent_str * level}}} {m.group(1)}")
continue
# Pure '}' → dedent then print
if stripped == "}":
level = max(level - 1, 0)
out_lines.append(f"{indent_str * level}{stripped}")
continue
# 'elseif' or 'else' alone → align with matching 'if'
if re.match(r"^(elseif|else)\b(?!.*\{)", stripped):
level = max(level - 1, 0)
out_lines.append(f"{indent_str * level}{stripped}")
continue
# Default: print at current indent
out_lines.append(f"{indent_str * level}{stripped}")
# Open a new block on lines ending with '{'
if re.match(r"^(if|elseif)\b.*\{$", stripped) or stripped.endswith("{"):
level += 1
return "\n".join(out_lines)
+15 -28
View File
@@ -4,16 +4,12 @@
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
@@ -41,22 +37,18 @@ update_sys_path(
# **********************************************************
# pylint: disable=wrong-import-position,import-error
import lsp_jsonrpc as jsonrpc
import lsp_utils as utils
import lsprotocol.types as lsp
from pygls import server, uris, workspace
from pygls.workspace.text_document import TextDocument
from common.load_data import standard_items
from tclint.parser import Parser
from tclint.lexer import TclSyntaxError
from tclint.format import Formatter, FormatterOpts
from tclint.violations import Violation
from plugins.poco_plugin import commands
from tools import checks, parser
from tools.semantic_tokens import _Highlighter, TOKEN_TYPES, TokenModifier
from tools.completion_items import completion
from tools.inlay_hint import InlayHintGenerator
from tools.symbols import OutlineVisitor
DIAGNOSTIC_SOURCE = "nx-post-support"
@@ -64,7 +56,7 @@ DIAGNOSTIC_SOURCE = "nx-post-support"
class TclLanguageServer(server.LanguageServer):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.parser = Parser()
self.parser = parser.CustomParser()
for command in commands:
self.parser._commands.update(command)
self.diagnostics = {}
@@ -197,14 +189,15 @@ def did_open(params: lsp.DidOpenTextDocumentParams) -> None:
"""LSP handler for textDocument/didOpen request."""
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
LSP_SERVER.compute_diagnostics(document)
completion.reset()
tree = LSP_SERVER.parser.parse(document.source)
tree.accept(completion, recurse=True)
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_SAVE)
def did_save(params: lsp.DidSaveTextDocumentParams) -> None:
"""LSP handler for textDocument/didSave request."""
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
tree = LSP_SERVER.parser.parse(document.source)
log_to_output(tree.pretty(2))
_ = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DID_CLOSE)
@@ -220,7 +213,6 @@ def did_change(params: lsp.DidChangeTextDocumentParams) -> None:
completion.reset()
tree = LSP_SERVER.parser.parse(document.source)
tree.accept(completion, recurse=True)
log_to_output(tree.pretty(2))
@LSP_SERVER.feature(
@@ -250,7 +242,7 @@ def document_diagnostic(params: lsp.DocumentDiagnosticParams):
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_COMPLETION)
def on_completion(params: lsp.CompletionParams) -> list[lsp.CompletionItem]:
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
_ = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
items = (
standard_items.tcl_keyword_list
@@ -261,15 +253,10 @@ def on_completion(params: lsp.CompletionParams) -> list[lsp.CompletionItem]:
return lsp.CompletionList(is_incomplete=False, items=items)
@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)
tree = LSP_SERVER.parser.parse(doc.source)
visitor = OutlineVisitor(LSP_SERVER.parser)
tree.accept(visitor, recurse=True)
return visitor.stack[0]
# @LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DOCUMENT_SYMBOL)
# def document_symbols(params: lsp.DocumentSymbolParams):
# doc = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
# return []
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_INLAY_HINT)
@@ -277,7 +264,7 @@ def inlay_hints(params: lsp.InlayHintParams):
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
tree = LSP_SERVER.parser.parse(document.source)
# Inlay Hints sammeln
# collect Inlay Hints
generator = InlayHintGenerator(completion.proc_signatures)
tree.accept(generator, recurse=True)
@@ -405,12 +392,12 @@ def formatting(params: lsp.DocumentFormattingParams) -> list[lsp.TextEdit] | Non
start = lsp.Position(line=0, character=0)
last_line = source.rsplit("\n", 1)[-1]
end = lsp.Position(line=source.count("\n"), character=len(last_line))
formatted = LSP_SERVER.format(doc, params.options)
if GLOBAL_SETTINGS.get("formatter", True):
source = LSP_SERVER.format(doc, params.options)
return [
lsp.TextEdit(
range=lsp.Range(start=start, end=end),
new_text=formatted,
new_text=source,
)
]
View File
+336 -123
View File
@@ -1,134 +1,347 @@
from tclint.parser import Parser, _strip_ws
from tclint.lexer import (
STATE_BRACEDWORD,
TOK_BACKSLASH_NEWLINE,
TOK_EOF,
TOK_LBRACE,
TOK_RBRACE,
TOK_WS,
TOK_NEWLINE,
TOK_ALPHA_CHARS,
TOK_RPAREN,
TOK_LPAREN,
TOK_DOLLAR,
TOK_QUOTE,
TOK_LBRACKET,
TOK_NUM_CHARS,
TclSyntaxError,
)
from tclint.parser import Parser
from tclint.commands import CommandArgError
from tclint.syntax_tree import (
BracedWord,
BareWord,
BinaryOp,
TernaryOp,
ParenExpression,
UnaryOp,
BracedExpression,
List,
QuotedWord,
Expression,
)
import ply.lex as lex
from typing import Tuple
TOK_BACKSLASH_NEWLINE = "BACKSLASH_NEWLINE"
TOK_BACKSLASH_SUB = "BACKSLASH_SUB"
TOK_NEWLINE = "NEWLINE"
TOK_SEMI = "SEMI"
TOK_WS = "WS"
TOK_QUOTE = "QUOTE"
TOK_ARG_EXPANSION = "ARG_EXPANSION"
TOK_LBRACE = "LBRACE"
TOK_RBRACE = "RBRACE"
TOK_STAR = "STAR"
TOK_LBRACKET = "LBRACKET"
TOK_RBRACKET = "RBRACKET"
TOK_DOLLAR = "DOLLAR"
TOK_LPAREN = "LPAREN"
TOK_RPAREN = "RPAREN"
TOK_HASH = "HASH"
TOK_ALPHA_CHARS = "ALPHA_CHARS"
TOK_NUM_CHARS = "NUM_CHARS"
TOK_NAMESPACE_SEP = "NAMESPACE_SEP"
TOK_CHAR = "CHAR"
TOK_CONTENTS = "CONTENTS"
TOK_EOF = None
STATE_BRACEDWORD = "bracedword"
class TclSyntaxError(Exception):
def __init__(self, message, start: Tuple[int, int], end: Tuple[int, int]):
super().__init__(message)
self.start = start
self.end = end
class _LexTable:
tokens = (
TOK_BACKSLASH_NEWLINE,
TOK_BACKSLASH_SUB,
TOK_NEWLINE,
TOK_SEMI,
TOK_WS,
TOK_QUOTE,
TOK_ARG_EXPANSION,
TOK_LBRACE,
TOK_RBRACE,
TOK_STAR,
TOK_LBRACKET,
TOK_RBRACKET,
TOK_DOLLAR,
TOK_LPAREN,
TOK_RPAREN,
TOK_HASH,
TOK_ALPHA_CHARS,
TOK_NUM_CHARS,
TOK_NAMESPACE_SEP,
TOK_CHAR,
TOK_CONTENTS,
)
# This defines a conditional lexing state for parsing braced words. This is a
# performance optimization; since there are few special characters in this context,
# we can use a smaller set of tokens to parse them faster. This has a large impact
# since most Tcl programs have a large number of braced words. Any token with
# `bracedword` in its name is included in this state. Tokens that are included in
# this state and the default state also include `INITIAL` in their name.
states = ((STATE_BRACEDWORD, "exclusive"),)
def _tok(self, t):
pos = (t.lexer.lineno, t.lexer.colno)
t.lexer.lineno += t.value.count("\n")
index = t.value.rfind("\n")
if index == -1:
t.lexer.colno += len(t.value)
else:
remaining = t.value[index + 1 :]
t.lexer.colno = len(remaining) + 1
t.value = (t.value, pos)
return t
# Priority important
def t_bracedword_INITIAL_BACKSLASH_NEWLINE(self, t):
r"\\\r?\n"
return self._tok(t)
# Priority important
def t_bracedword_INITIAL_BACKSLASH_SUB(self, t):
r"\\."
return self._tok(t)
def t_NEWLINE(self, t):
r"\n"
return self._tok(t)
def t_SEMI(self, t):
r";"
return self._tok(t)
# TODO: should use \s?
def t_WS(self, t):
r"[\t\v\f\r ]+"
return self._tok(t)
def t_QUOTE(self, t):
r'"'
return self._tok(t)
# Must be higher priority than LBRACE
def t_ARG_EXPANSION(self, t):
r"\{\*\}"
return self._tok(t)
def t_bracedword_INITIAL_LBRACE(self, t):
r"\{"
return self._tok(t)
def t_bracedword_INITIAL_RBRACE(self, t):
r"\}"
return self._tok(t)
def t_STAR(self, t):
r"\*"
return self._tok(t)
def t_LBRACKET(self, t):
r"\["
return self._tok(t)
def t_RBRACKET(self, t):
r"\]"
return self._tok(t)
def t_DOLLAR(self, t):
r"\$"
return self._tok(t)
def t_LPAREN(self, t):
r"\("
return self._tok(t)
def t_RPAREN(self, t):
r"\)"
return self._tok(t)
def t_HASH(self, t):
r"\#"
return self._tok(t)
# Valid non-numeric chars in variable names
def t_ALPHA_CHARS(self, t):
r"[A-Za-z_]+"
return self._tok(t)
# Valid numeric chars in variable names
# This is split up from the above to facilitate expression parsing, since
# e.g. 1eq1 can't be a single token.
def t_NUM_CHARS(self, t):
r"[0-9]+"
return self._tok(t)
def t_NAMESPACE_SEP(self, t):
r"::+"
return self._tok(t)
def t_bracedword_CONTENTS(self, t):
r"[^{}\\]+"
return self._tok(t)
# Catch-all. TODO: inefficient, should probably munch multiple chars
def t_CHAR(self, t):
r"."
return self._tok(t)
# Error handling rule
# TODO: do we need this? since we have a catch-all...
# there is a warning
def t_bracedword_INITIAL_error(self, t):
print("Illegal character '%s'" % t.value[0])
t.lexer.skip(1)
def __init__(self):
self.lexer = lex.lex(object=self)
self.lexer.lineno = 1
self.lexer.colno = 1
def new_lexer(self, pos=None):
lexer = self.lexer.clone()
lexer.lineno = 1
lexer.colno = 1
if pos is not None:
line, col = pos
lexer.lineno = line
lexer.colno = col
return lexer
# Calling `lex.lex()` performs an expensive reflection process to generate the lexer.
# This singleton class holds a preinitialized lexer that can then be cloned to create
# individual instances.
LexTable = _LexTable()
class Lexer:
def __init__(self, pos=None):
self.lexer = LexTable.new_lexer(pos)
self.current = None
def input(self, text):
self.lexer.input(text)
self.current = self.lexer.token()
def type(self):
if self.current is None:
return TOK_EOF
return self.current.type
def value(self):
if self.current is None:
return None
return self.current.value[0]
def pos(self):
if self.current is None:
return (self.lexer.lineno, self.lexer.colno)
return self.current.value[1]
def next(self):
self.current = self.lexer.token()
def expect(self, *tokens, message, pos):
if self.type() not in tokens:
self.next() # munch another token to update position
raise TclSyntaxError(message, pos, self.pos())
self.next()
def assert_(self, *tokens):
assert self.current.type in tokens
self.next()
class CustomParser(Parser):
@_strip_ws
def _parse_expression(self, ts):
op1 = self._parse_operand(ts)
expr = op1
def parse(self, script, pos=None):
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."
)
# Add TOK_BACKSLASH_NEWLINE to the tokens we need to skip
while ts.type() == TOK_BACKSLASH_NEWLINE:
ts.next()
return tree
# last condition is hack to break out of expression in case we're in ternary op
if ts.type() not in {
def parse_list(self, node):
"""Parse contents of node as Tcl list. This is a distinct entry point
that doesn't get used when generating the main syntax tree, but is used
in command-specific argument parsing.
"""
if isinstance(node, List):
return node
if node.contents is None:
raise CommandArgError(
"expected braced word or word without substitutions in argument"
" interpreted as list"
)
ts = Lexer(pos=node.contents_pos)
ts.input(node.contents)
DELIMITERS = {TOK_WS, TOK_BACKSLASH_NEWLINE, TOK_NEWLINE}
list_node = List(pos=node.pos, end_pos=node.end_pos)
while ts.type() is not TOK_EOF:
while ts.type() in DELIMITERS:
ts.next()
if ts.type() is TOK_EOF:
break
if ts.type() == TOK_LBRACE:
# we can reuse parse_braced_word, since it doesn't use
# substitutions in any case
list_node.add(self.parse_braced_word(ts))
elif ts.type() == TOK_QUOTE:
quote_word_pos = ts.pos()
ts.assert_(TOK_QUOTE)
bare_word_pos = ts.pos()
contents = ""
while ts.type() not in {TOK_QUOTE, TOK_EOF}:
contents += ts.value()
ts.next()
word = BareWord(contents, pos=bare_word_pos, end_pos=ts.pos())
ts.expect(
TOK_QUOTE,
message="reached EOF without finding match for quote",
pos=quote_word_pos,
)
list_node.add(QuotedWord(word, pos=quote_word_pos, end_pos=ts.pos()))
else:
pos = ts.pos()
contents = ""
while ts.type() not in {*DELIMITERS, TOK_EOF}:
contents += ts.value()
ts.next()
list_node.add(BareWord(contents, pos=pos, end_pos=ts.pos()))
return list_node
def parse_expression(self, node):
if node.contents is None:
raise CommandArgError(
"expected braced word or word without substitutions in argument"
" interpreted as expr"
)
ts = Lexer(pos=node.contents_pos)
ts.input(node.contents)
contents = self._parse_expression(ts)
ts.expect(
TOK_EOF,
TOK_RPAREN,
TOK_BACKSLASH_NEWLINE,
} and ts.value() not in {":", ","}:
if ts.value() == "?":
# weird hack to record operator
start = ts.pos()
ts.next()
q = BareWord("?", pos=start, end_pos=ts.pos())
message=f"expected end of expression, got {ts.value()}",
pos=ts.pos(),
)
if isinstance(node, BracedWord):
return BracedExpression(contents, pos=node.pos, end_pos=node.end_pos)
op2 = self._parse_expression(ts)
if ts.value() != ":":
start = ts.pos()
ts.next()
end = ts.pos()
raise TclSyntaxError(
"expected ':' to continue ternary expression", start, end
)
# weird hack again
start = ts.pos()
ts.next()
colon = BareWord(":", pos=start, end_pos=ts.pos())
op3 = self._parse_expression(ts)
expr = TernaryOp(
op1, q, op2, colon, op3, pos=op1.pos, end_pos=op3.end_pos
)
else:
operator = self._parse_operator(ts)
while ts.type() == TOK_BACKSLASH_NEWLINE:
ts.next()
op2 = self._parse_expression(ts)
expr = BinaryOp(op1, operator, op2, pos=op1.pos, end_pos=op2.end_pos)
if ts.type() not in (
TOK_RPAREN,
TOK_BACKSLASH_NEWLINE,
) and ts.value() not in {":", ","}:
ts.expect(TOK_EOF, message="expected end of expression", pos=ts.pos())
return expr
def _parse_operator(self, ts):
pos = ts.pos()
# hacky logic to handle parsing legal operators
# Skip any backslash-newlines before the operator
while ts.type() in {TOK_WS, TOK_BACKSLASH_NEWLINE, TOK_NEWLINE}:
ts.next()
if ts.value() in {"&&", "and"}: # Add explicit handling for logical AND
operator = ts.value()
ts.next()
return BareWord(operator, pos=pos, end_pos=ts.pos())
elif 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:
while ts.type() in {TOK_WS, TOK_BACKSLASH_NEWLINE, TOK_NEWLINE}:
ts.next()
if ts.value() in {"&&", "and"}: # Try again after whitespace
operator = ts.value()
ts.next()
else:
raise TclSyntaxError(
f"invalid operator in expression: {ts.value()}", pos, ts.pos()
)
return BareWord(operator, pos=pos, end_pos=ts.pos())
return Expression(contents, pos=node.pos, end_pos=node.end_pos)
+15 -1
View File
@@ -13,6 +13,7 @@ class TokenModifier(enum.IntFlag):
defaultLibrary = enum.auto()
definition = enum.auto()
declaration = enum.auto()
builtin = enum.auto()
@attrs.define
@@ -63,6 +64,20 @@ class _Highlighter(Visitor):
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],
)
)
)
pass
if routine.contents == "set" and command.args:
first_arg = command.args[0]
if hasattr(first_arg, "pos") and hasattr(first_arg, "value"):
@@ -135,7 +150,6 @@ class _Highlighter(Visitor):
)
def visit_var_sub(self, var_sub):
self.log_to_output(str(var_sub))
pass
def tokens(self) -> list[Token]:
-82
View File
@@ -1,82 +0,0 @@
from tclint.syntax_tree import Visitor, BareWord, BracedWord, Script, Command
from tclint.parser import Parser
import lsprotocol.types as lsp
class OutlineVisitor(Visitor):
def __init__(self, parser: Parser):
self.parser = parser
self.stack = [[]] # Root symbol list
def _range(self, node) -> lsp.Range:
line = node.line - 1
col = node.col - 1
if node.end_pos:
end_line = node.end_pos[0] - 1
end_col = node.end_pos[1] - 1
else:
end_line = line
end_col = col + 1
return lsp.Range(
start=lsp.Position(line=line, character=col),
end=lsp.Position(line=end_line, character=end_col),
)
def _add(self, name: str, kind: lsp.SymbolKind, node, children=None):
symbol = lsp.DocumentSymbol(
name=name,
kind=kind,
range=self._range(node),
selection_range=self._range(node),
children=children or [],
)
self.stack[-1].append(symbol)
return symbol
def visit_script(self, script):
for child in script.children:
child.accept(self, recurse=False)
def visit_command(self, command: Command):
if not isinstance(command.routine, BareWord):
return
name = command.routine.contents
# --- NAMESPACE EVAL ---
if name == "namespace" and len(command.args) >= 3:
subcmd = command.args[0]
if isinstance(subcmd, BareWord) and subcmd.contents == "eval":
ns_arg = command.args[1]
ns_name = (
ns_arg.contents if isinstance(ns_arg, BareWord) else "<namespace>"
)
ns_body = command.args[2]
ns_symbol = self._add(
ns_name, lsp.SymbolKind.Namespace, command, children=[]
)
self.stack.append(ns_symbol.children)
if isinstance(ns_body, BracedWord):
try:
subtree = self.parser.parse_script(ns_body)
subtree.accept(self, recurse=False)
except Exception as e:
print(f"Failed parsing namespace body: {e}")
self.stack.pop()
# --- PROC ---
elif name == "proc" and len(command.args) >= 1:
proc_arg = command.args[0]
proc_name = (
proc_arg.contents if isinstance(proc_arg, BareWord) else "<proc>"
)
self._add(proc_name, lsp.SymbolKind.Function, command)
# --- SET ---
elif name == "set" and len(command.args) >= 1:
var_arg = command.args[0]
var_name = var_arg.contents if isinstance(var_arg, BareWord) else "<var>"
self._add(var_name, lsp.SymbolKind.Variable, command)
+3 -3
View File
@@ -28,8 +28,8 @@ proc SERVICE_spacer_output {type {length 20} {line_num 0} {output 1}} {
LIB_GE_message [string repeat $type $length] "output_$output" $line_num
}
SERVICE_spacer_output "*" 50 0 1
SERVICE_remove_file $filename
SERVICE_spacer_output "*" 2 0 0
#_________________________________________________________________________________________________
# <Documentation>
@@ -93,7 +93,7 @@ proc SERVICE_output_handling {handler} {
#_________________________________________________________________________________________________
proc SERVICE_get_tool_data {} {
global mom_tool_data
global mom_operation_info
global mom_operation_info
set mom_tool_data(toollist) ""
set operations $::mom_operation_name_list