VSIX y Herramientas de Editor (Beta)
[!WARNING] Aviso de Estado Beta:
El paquetegram.vsix(extracción de esquemas, coloreado de tokens, empaquetado de.vsixy servidor de lenguaje JSON-RPC integrado) se encuentra actualmente en fase Beta. Los flujos de trabajo principales son completamente funcionales y con cero dependencias externas, pero las heurísticas de mapeo de reglas a LSP y las opciones de generación del manifiesto están bajo desarrollo activo.
gram.vsix te permite adjuntar metadatos declarativos de editor directamente a tus clases RuleItem y definiciones de Keyword, y utilizar esos metadatos para:
- Renderizar previsualizaciones con resaltado de sintaxis ANSI TrueColor de 24 bits en la terminal (
ASTColorResolver). - Extraer un esquema canónico del lenguaje serializable en JSON (
VSIXAnalyzer). - Generar los archivos completos de una extensión de Visual Studio Code (
package.json,language-configuration.json, TextMate.tmLanguage.json,snippets.jsony cliente LSP enextension.js) medianteVSIXGenerator. - Empaquetar un archivo
.vsixsin Node.js e instalarlo en VS Code (pack_vsix,install_extension,up).
1. Mapas Declarativos de Editor en RuleItem
Cualquier RuleItem con nodeable = True puede definir cinco atributos de diccionario opcionales:
from gram import combinators as cmb
from gram import lexer
class ImportDecl(cmb.RuleItem):
name = "IMPORT_DECL"
nodeable = True # Requerido para mapas de ASTColorResolver y VSIXAnalyzer
description = "Importa un módulo externo dentro del ámbito actual."
entrypoint = cmb.Seq(
cmb.MatchKeyword("import"),
cmb.MatchToken(lexer.Token.IDENT),
cmb.MatchToken(lexer.Token.SEMICOLON),
)
# 1. Coloreado de tokens (colores hexadecimales o ámbitos TextMate)
colors = {
"import": "#FF79C6",
"@import:1": "#8BE9FD",
-1: "#6272A4",
}
# 2. Autocompletado de palabras clave / prefijos
autocomplete = {
0: "import",
}
# 3. Sugerencias de valores (lista/tupla estática o invocable sin argumentos)
suggestions = {
1: ["math", "sys", "io", "http"],
}
# 4. Pistas de tipo / fragmentos (hints)
hints = {
0: "import <nombre_modulo>;",
}
# 5. Pistas integradas de parámetros (playhints / inlay hints)
playhints = {
1: "module",
}
[!IMPORTANT] Requisito de
nodeable = True:
ASTColorResolverrecorre los nodosASTRefen el AST parseado y hace coincidirref_node.namecon las clasesRuleItemregistradas. Si una regla definenodeable = False, no se emite ningún nodoASTRefpara dicha regla, yVSIXAnalyzerregistra una advertencia:"Rule '<name>' defines editor maps but nodeable is False."
2. Cómo Resuelve Claves en colors el TokenSelector
Cuando ASTColorResolver evalúa un nodo ASTRef coincidente, aplana todas las instancias de TokenType dentro de ese nodo en orden fuente (collect_node_tokens(ref_node)) y resuelve cada clave de RuleItem.colors utilizando TokenSelector.resolve_indices(key, tokens):
| Formato de Clave Selectora | Ejemplo | Comportamiento de Resolución |
|---|---|---|
int positivo o str numérico |
0, 1, "0" |
Coincide con la posición del token basada en índice 0 dentro de la lista aplanada de tokens del ASTRef. |
int negativo o str numérico |
-1, "-1" |
Coincide relativo al final de la lista de tokens del ASTRef (len(tokens) + idx, donde -1 es el último token). |
Ancla (@TOKEN) |
"@IDENT", "@=" |
Coincide con cada token en la regla cuyo tok.token.name o lexema str(tok.value) sea igual al nombre del ancla. |
Ancla con Desplazamiento (@TOKEN:±offset) |
"@import:1", "@ASSIGN:-1" |
Encuentra cada token coincidente con TOKEN (por tipo de token o lexema) y selecciona el token en i + offset (ej.: "@import:1" selecciona el token inmediatamente posterior a import). |
| Nombre de Tipo de Token o Lexema | "IDENT", "return", ";" |
Coincide con cada token cuyo tok.token.name o str(tok.value) coincida con la cadena. |
Nombre de WordGroup de Keywords |
"types" |
Coincide con cualquier Token.KEYWORD cuyo Keyword.group registrado sea igual al nombre del grupo. |
Reglas de Precedencia y Fallback
- El Hijo Sobrescribe al Padre (
child_overrides_parent=True): Por defecto, si tanto una reglaASTRefexterior como una reglaASTRefanidada interior asignan un color a la misma ocurrencia de un token, gana el color de la regla secundaria interior. - Fallback al Léxico de Palabras Clave: Cualquier
Token.KEYWORDal que ningún mapaRuleItem.colorsle haya asignado color recurre automáticamente alhex_colorregistrado víalexer.words.add_keyword(..., hex_color=...).
3. Previsualización Terminal en ANSI TrueColor de 24 Bits
Puedes resolver los colores de los tokens en cualquier AST parseado y generar una cadena formateada en TrueColor ANSI de 24 bits usando resolve_ast_colors y preview_colored_source:
from gram import combinators as cmb
from gram import lexer, parser, vsix
lexer.words.clear_all()
lexer.words.add_keyword("let", hex_color="#FF79C6", allow_override=True)
class LetDecl(cmb.RuleItem):
nodeable = True
entrypoint = cmb.Seq(
cmb.MatchKeyword("let"),
cmb.MatchToken(lexer.Token.IDENT),
cmb.MatchToken(lexer.Token.ASSIGN),
cmb.MatchToken(lexer.Token.NUMBER),
)
colors = {
"@ASSIGN:-1": "#50FA7B", # Identificador antes de '='
"ASSIGN": "#FF79C6", # Símbolo '='
-1: "#BD93F9", # Literal numérico al final
}
class Program(cmb.RuleItem):
entrypoint = cmb.Alt(cmb.Ref(LetDecl))
source = "let speed = 100"
tokens = lexer.Lexer(source).process()
ast_roots = parser.Parser(tokens).parse(Program)
# 1. Inspeccionar instancias ColoredToken resueltas:
colored_tokens = vsix.resolve_ast_colors(ast_roots)
for ct in colored_tokens:
print(f"{ct.token.value!r:8} -> {ct.color} (desde {ct.rule_name}[{ct.token_index!r}])")
# 2. Generar cadena ANSI TrueColor de 24 bits para mostrar en consola:
ansi_preview = vsix.preview_colored_source(source, ast_roots)
print(ansi_preview)
Salida:
'speed' -> #50FA7B (desde LetDecl['@ASSIGN:-1'])
'=' -> #FF79C6 (desde LetDecl['ASSIGN'])
100 -> #BD93F9 (desde LetDecl[-1])
'let' -> #FF79C6 (desde LexerKeyword[0])
4. Generar y Empaquetar una Extensión de VS Code
Paso 1: Declarar Metadatos de la Extensión (vsix.declare_info)
Utiliza vsix.declare_info() (o las funciones granulares set_package, set_language, set_author, set_repository, set_meta) para configurar el manifiesto de la extensión:
from gram import vsix
vsix.declare_info(
name="my-dsl-support",
display_name="My DSL Language Support",
description="Resaltado de sintaxis, fragmentos y LSP para My DSL",
version="0.1.0",
publisher="my-org",
language_id="mydsl",
language_name="MyDSL",
file_extensions=[".mydsl"],
)
Paso 2: Analizar la Gramática (VSIXAnalyzer) y Exportar Artefactos (VSIXGenerator)
# 1. Extraer esquema canónico desde el RuleItem punto de entrada raíz:
schema = vsix.VSIXAnalyzer(output="vscode").process(Program)
# 2. Generar archivos de extensión para VS Code en ./vscode_extension:
generator = vsix.VSIXGenerator(schema)
artifacts = generator.export("./vscode_extension")
# Escribe:
# - ./vscode_extension/package.json
# - ./vscode_extension/language-configuration.json
# - ./vscode_extension/syntaxes/mydsl.tmLanguage.json
# - ./vscode_extension/snippets/snippets.json
# - ./vscode_extension/extension.js
Paso 3: Empaquetar .vsix e Instalar (pack_vsix / up)
vsix.pack_vsix() construye un archivo zip .vsix con Open Packaging Conventions (OPC) directamente en Python sin requerir Node.js ni vsce.
vsix.up() orquesta la exportación, el empaquetado .vsix, la instalación local en ~/.vscode/extensions y el inicio opcional de VS Code en una sola llamada:
# Empaquetar ./vscode_extension en un archivo .vsix independiente sin instalar:
vsix_file = vsix.pack_vsix("./vscode_extension")
# O exportar, empaquetar e instalar en VS Code en un solo paso:
status = vsix.up(
generator,
pack=True,
install=True,
launch=False,
)
5. Servidor de Lenguaje Integrado (gram.vsix.lsp)
El cliente extension.js generado inicia automáticamente el servidor de lenguaje en Python puro por stdio de Gram:
python -u -m gram.vsix.lsp.server --entrypoint mi_modulo:Program --workspace /ruta/al/workspace
Internamente, LSPState (gram.vsix.lsp.state.LSPState) utiliza Diagnostics.hook_errors con ErrorAction.SUPPRESS_RAISE y config.ERROR_EXIT_ON_ERROR = False para ejecutar Lexer y Parser en cada evento textDocument/didOpen y textDocument/didChange, convirtiendo las instancias capturadas de Error en diagnósticos LSP junto con respuestas para autocompletado, hover e inlay hints.