Files
Print_server/template_editor_plan.md
2026-01-22 11:13:07 +01:00

64 KiB

Receipt Template Language (RTL) - Project Specification


Phase 1: Core Template Engine

1. Overview

A file-based template system for configuring receipt layouts per receipt type and printer model. Replaces hardcoded C# converters with editable .template files.

Deliverables

  • New project: Inspectron.Epson.Templates
  • Template language lexer, parser, and interpreter
  • File-based configuration loader
  • Integration with existing IReceiptConverterFactory
  • Fallback template for undefined combinations
  • Unit tests

2. Project Structure

Inspectron.Epson.Templates/
├── Inspectron.Epson.Templates.csproj
├── Language/
│   ├── Lexer.cs
│   ├── Parser.cs
│   ├── Tokens.cs
│   └── Nodes/
│       ├── TemplateNode.cs
│       ├── TextNode.cs
│       ├── StyledTextNode.cs
│       ├── BindingNode.cs
│       ├── SeparatorNode.cs
│       ├── RowNode.cs
│       ├── ColumnNode.cs
│       ├── IfNode.cs
│       ├── ForeachNode.cs
│       └── EmptyLineNode.cs
├── Interpreter/
│   ├── TemplateInterpreter.cs
│   ├── DataContext.cs
│   └── InterpreterException.cs
├── Configuration/
│   ├── TemplateConfiguration.cs
│   ├── TemplateAssignment.cs
│   ├── PrinterProfile.cs
│   ├── PrinterProfileRegistry.cs
│   └── TemplateResolver.cs
├── Storage/
│   ├── ITemplateStorage.cs
│   └── FileTemplateStorage.cs
├── TemplateEngine.cs
├── TemplateReceiptConverter.cs
└── TemplateReceiptConverterFactory.cs

3. File System Layout

templates/
├── kitchen-default.template        # Default kitchen receipt
├── kitchen-u220.template           # Kitchen receipt for TM-U220II
├── bar-default.template            # Bar receipt
├── final-receipt.template          # Customer receipt
├── orders-overview.template        # Orders overview
├── fallback.template               # Used when no match found
├── assignments.json                # Maps (receiptType, printer) → template
└── samples/                        # Sample data for preview/testing
    ├── kitchen-default.json
    ├── bar-default.json
    ├── final-receipt.json
    └── orders-overview.json

4. Template Language Specification

4.1 Line Types

Line Type Syntax Example
Styled Text #styles# content #bold,center# {title}
Plain Text content Hello World
Separator ---, ===, ***, ~~~ ---
Row Start @row ratios [styles] @row 60:40 bold
Row Content content | content {name} | >{price}
Row End @endrow @endrow
Condition @if condition @if comment
Loop @foreach item in collection @foreach dish in dishes
Block End @end @end
Empty (blank line)

4.2 Styles

Style Description
bold Bold text
big Large font (2x magnification)
red Red color (two-color printers only)
center Center-aligned
right Right-aligned
spacing:N Set line spacing (0-255)
spacing:default Reset to default spacing

Syntax: #style1,style2,...# content

Examples:

#bold,center,big# {title}
#red# WARNING
#spacing:0,bold# Compact line

4.3 Data Binding

Syntax: {path} or {path:format}

Path navigation:

{title}                 // Root property
{order.date}            // Nested property
{items.count}           // Collection count

Format specifiers:

{price:F2}              // Decimal, 2 places → "12.50"
{date:dd-MMM-yy}        // Date → "25-Jan-26"
{date:HH:mm}            // Time → "14:30"
{number:D4}             // Padded integer → "0042"

Missing data behavior: Returns empty string (silent).

4.4 Separators

---     // Dashed line (fills line width)
===     // Equals line
***     // Asterisk line
~~~     // Tilde line

Minimum 3 characters. Fills PrinterProfile.LineWidth.

4.5 Rows (Columns)

Multi-line syntax:

@row 60:40
{leftContent} | >{rightContent}
@endrow

Single-line shorthand:

@row 60:40 | {left} | >{right} |

Ratios:

@row 50:50           // Two equal columns
@row 60:30:10        // Three columns (percentages)
@row *:20            // First fills remainder, second is 20%
@row 30:*:20         // Middle fills remainder

Column alignment (prefix in content):

{text}      // Left-aligned (default)
>{text}     // Right-aligned
^{text}     // Center-aligned

Row styles (optional, applied to all columns):

@row 50:50 bold
@row 70:30 big,red

4.6 Conditionals

Syntax:

@if condition
  content
@end

Conditions:

@if propertyName              // Truthy (not null, not empty, not false, not 0)
@if !propertyName             // Falsy
@if items.count > 0           // Comparison
@if status == "active"        // String equality
@if total >= 100              // Numeric comparison

Operators: ==, !=, >, <, >=, <=

4.7 Loops

Syntax:

@foreach item in collection
  content using {item.property}
@end

Loop variables:

{item}              // Current item (if primitive)
{item.property}     // Item property
{item._index}       // 0-based index
{item._number}      // 1-based number
{item._first}       // true if first iteration
{item._last}        // true if last iteration

Nesting:

@foreach gang in gangs
  #red# {gang.name}
  @foreach dish in gang.dishes
    {dish.number}x {dish.name}
  @end
@end

5. Configuration

5.1 assignments.json

{
  "assignments": [
    {
      "receiptType": "Receipt",
      "printerProfile": null,
      "templateFile": "final-receipt.template"
    },
    {
      "receiptType": "WorkareaTicket",
      "printerProfile": null,
      "templateFile": "kitchen-default.template"
    },
    {
      "receiptType": "WorkareaTicket",
      "printerProfile": "tm-u220ii",
      "templateFile": "kitchen-u220.template"
    },
    {
      "receiptType": "NextCourse",
      "printerProfile": null,
      "templateFile": "kitchen-default.template"
    },
    {
      "receiptType": "OrdersOverview",
      "printerProfile": null,
      "templateFile": "orders-overview.template"
    }
  ],
  "fallbackTemplate": "fallback.template"
}

5.2 Printer Profiles (built-in)

Profile ID Printer LineWidth BigLineWidth SupportsRed
tm-t30iii TM-T30III 48 24 false
tm-u220ii TM-U220II 33 20 true

Mapped from printer ID byte:

  • 0x01tm-t30iii
  • 0x13tm-u220ii

5.3 Resolution Priority

  1. Exact match: receiptType + printerProfile
  2. Type only: receiptType + printerProfile: null
  3. Fallback: fallbackTemplate

6. Public API

6.1 TemplateEngine

namespace Inspectron.Epson.Templates;

public class TemplateEngine
{
    public TemplateEngine(string templatesDirectory);

    /// <summary>
    /// Renders a template with the given data.
    /// </summary>
    public List<PrintCommand> Render(
        string templateContent,
        string jsonData,
        PrinterProfile profile);

    /// <summary>
    /// Parses template and returns errors/warnings (for validation).
    /// </summary>
    public TemplateValidationResult Validate(string templateContent);
}

public class TemplateValidationResult
{
    public bool IsValid { get; }
    public List<TemplateError> Errors { get; }
    public List<TemplateWarning> Warnings { get; }
}

public class TemplateError
{
    public int Line { get; }
    public int Column { get; }
    public string Message { get; }
    public string Code { get; }  // e.g., "RTL001"
}

public class TemplateWarning
{
    public int Line { get; }
    public int Column { get; }
    public string Message { get; }
    public string Code { get; }  // e.g., "RTL100"
}

6.2 TemplateReceiptConverterFactory

namespace Inspectron.Epson.Templates;

public class TemplateReceiptConverterFactory : IReceiptConverterFactory
{
    public TemplateReceiptConverterFactory(string templatesDirectory);

    /// <summary>
    /// Creates a converter for the given receipt type and printer.
    /// Falls back to fallback template if no match found.
    /// </summary>
    public IReceiptConverter Create(int receiptType, byte printerId);

    /// <summary>
    /// Reloads templates and assignments from disk.
    /// </summary>
    public void Reload();
}

6.3 Integration Example

// In EpsonPrintService startup
var converterFactory = new TemplateReceiptConverterFactory("./templates");

// In DI registration (Ninject)
kernel.Bind<IReceiptConverterFactory>()
    .ToConstant(converterFactory)
    .InSingletonScope();

// Usage remains unchanged
var converter = converterFactory.Create(receiptType, printerId);
var commands = converter.Convert(jsonContent);

7. Error Codes

Errors (prevent rendering)

Code Message
RTL001 Unexpected token '{token}' at line {line}
RTL002 Unclosed block '@{block}' started at line {line}
RTL003 '@end' without matching '@if' or '@foreach'
RTL004 Invalid style '{style}'
RTL005 Invalid ratio format '{ratio}'
RTL006 Column count mismatch: expected {expected}, got {actual}
RTL007 Invalid condition syntax
RTL008 Invalid binding path '{path}'
RTL009 Unclosed binding '{' at line {line}
RTL010 Unclosed style marker '#' at line {line}

Warnings (render anyway)

Code Message
RTL100 Unused loop variable '{variable}'
RTL101 Empty @if block
RTL102 Empty @foreach block
RTL103 Style '{style}' has no effect on this printer

8. Default Templates

Provide default templates matching current hardcoded behavior:

  • kitchen-default.template - matches KitchenReceiptConverter
  • kitchen-u220.template - compact version for narrow printers
  • bar-default.template - matches BarReceiptConverter
  • final-receipt.template - matches FinalReceiptConverter
  • orders-overview.template - matches OrderItemsReceiptConverter
  • fallback.template - minimal template that dumps JSON fields

9. Testing

Unit Tests

Inspectron.Epson.Templates.Tests/
├── Lexer/
│   ├── LexerBasicTests.cs
│   ├── LexerStyleTests.cs
│   ├── LexerBindingTests.cs
│   └── LexerControlFlowTests.cs
├── Parser/
│   ├── ParserBasicTests.cs
│   ├── ParserRowTests.cs
│   ├── ParserNestingTests.cs
│   └── ParserErrorTests.cs
├── Interpreter/
│   ├── InterpreterTextTests.cs
│   ├── InterpreterBindingTests.cs
│   ├── InterpreterConditionTests.cs
│   ├── InterpreterLoopTests.cs
│   └── InterpreterRowTests.cs
├── Configuration/
│   ├── TemplateResolverTests.cs
│   └── PrinterProfileTests.cs
└── Integration/
    ├── KitchenReceiptTests.cs
    ├── FinalReceiptTests.cs
    └── FallbackTests.cs

Test Coverage Goals

  • Lexer: All token types, edge cases, error handling
  • Parser: All node types, nesting, malformed input
  • Interpreter: All features, format specifiers, missing data
  • Integration: Match output of existing converters

10. Migration Plan

  1. Create Inspectron.Epson.Templates project
  2. Implement and test template engine
  3. Create default templates matching current output
  4. Add TemplateReceiptConverterFactory to DI as alternative
  5. Integration test: compare output of both factories
  6. Switch DI registration to use template factory
  7. Remove legacy converter code (optional, can keep as reference)

Phase 2: Template Editor (VS Code Extension + C# Language Server)

1. Overview

VS Code extension for creating and editing receipt templates with live preview, syntax highlighting, autocomplete, and validation. The extension uses a C# Language Server (LSP) that reuses the lexer, parser, and interpreter from Phase 1. The VS Code extension is a thin TypeScript client that connects to the language server.

Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│                              VS Code                                     │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │  vscode-rtl (TypeScript)                                          │  │
│  │  - Language client (connects to LSP)                              │  │
│  │  - Preview webview (renders HTML)                                 │  │
│  │  - Assignments webview                                            │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                    │ LSP (JSON-RPC over stdio)          │
│                                    ▼                                     │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │  Inspectron.Epson.Templates.LanguageServer (C#)                   │  │
│  │  - Semantic tokens (syntax highlighting)                          │  │
│  │  - Completion provider                                            │  │
│  │  - Diagnostics (validation)                                       │  │
│  │  - Hover provider                                                 │  │
│  │  - Folding ranges                                                 │  │
│  │  - Document symbols                                               │  │
│  │  - Custom requests (preview rendering)                            │  │
│  │                                                                   │  │
│  │  References: Inspectron.Epson.Templates (Phase 1)                 │  │
│  └───────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────┘

Deliverables

  • C# Language Server: Inspectron.Epson.Templates.LanguageServer
  • VS Code extension: vscode-rtl (thin TypeScript client)
  • Language registration for .template files
  • Semantic token provider for syntax highlighting (via LSP)
  • Completion provider (via LSP)
  • Diagnostic provider for real-time validation (via LSP)
  • Webview panel for live preview with sample data
  • Webview panel for template assignment management

Advantages

  • No code duplication - reuses Phase 1 lexer, parser, interpreter
  • Single source of truth - language behavior defined once in C#
  • Mature editing features - VS Code provides find/replace, multi-cursor, undo/redo, git
  • Cross-platform - .NET 8 runs on Windows, macOS, Linux
  • Easy distribution - VSIX bundles extension + language server executable

2. Project Structure

2.1 Language Server (C#)

Inspectron.Epson.Templates.LanguageServer/
├── Inspectron.Epson.Templates.LanguageServer.csproj
├── Program.cs                          # Entry point, stdio server setup
├── RtlLanguageServer.cs                # Main server class
├── Handlers/
│   ├── TextDocumentSyncHandler.cs      # Document open/change/close
│   ├── SemanticTokensHandler.cs        # Syntax highlighting
│   ├── CompletionHandler.cs            # Autocomplete
│   ├── DiagnosticHandler.cs            # Validation errors/warnings
│   ├── HoverHandler.cs                 # Hover information
│   ├── FoldingRangeHandler.cs          # Code folding
│   ├── DocumentSymbolHandler.cs        # Outline/breadcrumbs
│   └── CustomRequestHandlers.cs        # Preview rendering, schema extraction
├── Services/
│   ├── DocumentManager.cs              # Track open documents
│   ├── DiagnosticService.cs            # Run validation, publish diagnostics
│   ├── CompletionService.cs            # Build completion items
│   ├── SemanticTokenService.cs         # Generate semantic tokens
│   ├── SchemaService.cs                # Extract schema from sample JSON
│   └── ScopeTracker.cs                 # Track @foreach loop variables
└── Models/
    ├── RtlSemanticTokenTypes.cs        # Token type definitions
    └── RtlSemanticTokenModifiers.cs    # Token modifier definitions

2.2 VS Code Extension (TypeScript - thin client)

vscode-rtl/
├── package.json                    # Extension manifest
├── tsconfig.json                   # TypeScript configuration
├── webpack.config.js               # Bundler configuration
├── .vscodeignore                   # Files to exclude from package
├── language-configuration.json     # Bracket matching, auto-closing pairs
├── snippets/
│   └── rtl.snippets.json           # Code snippets (@if, @foreach, @row)
├── src/
│   ├── extension.ts                # Extension entry point
│   ├── languageClient.ts           # LSP client setup and management
│   ├── preview/
│   │   ├── previewPanel.ts         # Webview panel management
│   │   └── previewStyles.css       # Receipt visual styling
│   ├── assignments/
│   │   └── assignmentsPanel.ts     # Assignments editor webview
│   └── utils/
│       └── serverPath.ts           # Locate language server executable
├── media/
│   ├── preview.css                 # Receipt preview styling
│   ├── assignments.css             # Assignments editor styling
│   └── icons/
│       ├── template.svg            # File icon
│       └── preview.svg             # Preview button icon
├── server/                         # Bundled language server (published .exe/.dll)
│   └── (built from C# project)
└── test/
    └── extension.test.ts           # Basic extension activation tests

3. UI Layout

The extension integrates with VS Code's native UI:

┌─────────────────────────────────────────────────────────────────────────────┐
│  File   Edit   Selection   View   Go   Run   Terminal   Help                │
├─────────────────────────────────────────────────────────────────────────────┤
│ EXPLORER            │ kitchen-default.template              │ RTL Preview   │
│ ┌─────────────────┐ │ ─────────────────────────────────────│───────────────│
│ │ ▼ TEMPLATES     │ │  1│#bold,center,big# {title}         │┌─────────────┐│
│ │   kitchen-def...│ │  2│---                               ││ Warme Küche ││
│ │   kitchen-u220  │ │  3│                                  ││─────────────││
│ │   bar-default   │ │  4│@row 60:40                        ││             ││
│ │   final-receipt │ │  5│{date:dd-MMM} | >{orderNumber}    ││25-Jan Nr:42 ││
│ │   orders-overv..│ │  6│@endrow                           ││             ││
│ │   fallback      │ │  7│#center# {waiter}                 ││Max Musterma.││
│ │                 │ │  8│                                  ││             ││
│ │ ▼ SAMPLES       │ │  9│@foreach dish in dishes           ││Tisch: 12    ││
│ │   kitchen-def...│ │ 10│  {dish.quantity}x {dish.name}    ││─────────────││
│ │   bar-default...│ │ 11│@end                              ││2x Tomatensup││
│ │                 │ │                                      ││1x Schnitzel ││
│ │ ▼ RTL ASSIGNMENTS│ │                                      ││  - Pommes   ││
│ │   Receipt → ...│ │                                      ││  + Reis     ││
│ │   WorkareaTic..│ │                                      │└─────────────┘│
│ │   NextCourse →.│ │                                      │               │
│ └─────────────────┘ │                                      │Printer: ▼    │
│                     │                                      │ TM-T30III    │
├─────────────────────┴──────────────────────────────────────┴───────────────┤
│ PROBLEMS  OUTPUT  DEBUG CONSOLE  TERMINAL                                   │
│ ┌──────────────────────────────────────────────────────────────────────────┐│
│ │ ⚠ kitchen-default.template [2]                                          ││
│ │   ⚠ Line 10: Unused loop variable 'dish' in nested scope       [RTL100] ││
│ │ ❌ fallback.template [1]                                                 ││
│ │   ❌ Line 5: Unclosed '@foreach' block started at line 3        [RTL002] ││
│ └──────────────────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────────────────┤
│ RTL │ Ln 7, Col 12 │ UTF-8 │ CRLF │ {} kitchen-default.json │ ✓           │
└─────────────────────────────────────────────────────────────────────────────┘

UI Components

Component VS Code Feature Implementation
Template list Explorer tree view Native file explorer or custom TreeDataProvider
Sample data list Explorer tree view Custom TreeDataProvider
Assignments list Explorer tree view Custom TreeDataProvider with inline editing
Editor Native text editor Language client connects to C# language server
Syntax highlighting Semantic tokens Language server provides tokens via LSP
Autocomplete Completion items Language server provides completions via LSP
Validation Problems panel Language server publishes diagnostics via LSP
Preview Webview panel TypeScript requests rendered HTML from language server
Status bar Status bar items Shows current sample file, printer profile

4. Features

4.1 Language Registration (VS Code Extension)

package.json contribution:

{
  "contributes": {
    "languages": [{
      "id": "rtl",
      "aliases": ["Receipt Template Language", "RTL"],
      "extensions": [".template"],
      "configuration": "./language-configuration.json",
      "icon": { "light": "./media/icons/template.svg", "dark": "./media/icons/template.svg" }
    }]
  }
}

Note: No TextMate grammar needed - syntax highlighting is provided via LSP semantic tokens.

language-configuration.json:

{
  "comments": { "lineComment": "//" },
  "brackets": [["@if", "@end"], ["@foreach", "@end"], ["@row", "@endrow"]],
  "autoClosingPairs": [
    { "open": "{", "close": "}" },
    { "open": "#", "close": "#" }
  ],
  "surroundingPairs": [
    { "open": "{", "close": "}" },
    { "open": "#", "close": "#" }
  ]
}

4.2 Syntax Highlighting (LSP Semantic Tokens)

The language server provides semantic tokens via the textDocument/semanticTokens/full LSP method.

C# Semantic Token Types (SemanticTokensHandler.cs):

public static class RtlSemanticTokenTypes
{
    public const string Keyword = "keyword";           // @if, @foreach, @end, @row, @endrow
    public const string Variable = "variable";         // {binding}
    public const string Property = "property";         // {object.property}
    public const string Decorator = "decorator";       // #style#
    public const string String = "string";             // Style names: bold, center
    public const string Number = "number";             // Ratios: 60:40
    public const string Operator = "operator";         // ==, !=, >, <, |, >, ^
    public const string Comment = "comment";           // Separators: ---, ===
    public const string Type = "type";                 // Format specifiers: F2, dd-MMM
}

Token mapping:

Element Token Type Default Color
@if, @foreach, @end keyword Purple
@row, @endrow keyword Purple
#...# markers decorator Blue
Style names bold, center string Green
{...} bindings variable Teal
Property paths .property property Teal
Format specifiers :F2 type Orange
Ratios 60:40 number Light Green
Separators --- comment Gray
Column delimiter | operator Yellow
Alignment >, ^ operator Orange
Comparison ==, != operator Red

4.3 Code Folding (LSP Folding Ranges)

FoldingRangeHandler.cs:

public class FoldingRangeHandler : IFoldingRangeHandler
{
    public Task<Container<FoldingRange>?> Handle(FoldingRangeRequestParam request, CancellationToken ct)
    {
        var ranges = new List<FoldingRange>();
        // Parse document, find @if...@end, @foreach...@end, @row...@endrow blocks
        // Add FoldingRange for each block
        return Task.FromResult<Container<FoldingRange>?>(new Container<FoldingRange>(ranges));
    }
}

Supports:

  • Fold @if...@end blocks
  • Fold @foreach...@end blocks
  • Fold @row...@endrow blocks
  • Nested folding

4.4 Autocomplete (LSP Completion)

CompletionHandler.cs triggers:

Trigger Context Completions
# Line start or after # Style keywords: bold, big, red, center, right, spacing:
, Inside #...# Style keywords
@ Line start Control flow: if, foreach, end, row, endrow
{ Anywhere Root data properties from sample JSON + active loop variables
. Inside {...} Child properties based on path + loop metadata (_index, _first, etc.)
: Inside {...} after property Format specifiers: F2, D4, dd-MMM-yy, HH:mm
in After @foreach x Collection properties from schema

CompletionService.cs features:

  • Context-aware (detects binding, style, control flow context)
  • Completion item kinds (Property, Keyword, Snippet, etc.)
  • Documentation for format specifiers
  • Loop variable scope tracking via ScopeTracker
public class CompletionService
{
    private readonly SchemaService _schemaService;
    private readonly ScopeTracker _scopeTracker;

    public List<CompletionItem> GetCompletions(DocumentUri uri, Position position)
    {
        var context = DetectContext(uri, position);
        return context switch
        {
            CompletionContext.Style => GetStyleCompletions(),
            CompletionContext.ControlFlow => GetControlFlowCompletions(),
            CompletionContext.Binding => GetBindingCompletions(uri, position),
            CompletionContext.BindingProperty => GetPropertyCompletions(uri, position),
            CompletionContext.BindingFormat => GetFormatCompletions(),
            _ => new List<CompletionItem>()
        };
    }
}

4.5 Diagnostics (LSP PublishDiagnostics)

DiagnosticService.cs:

  • Validates on document open and change (debounced)
  • Uses Phase 1 TemplateEngine.Validate() method
  • Publishes diagnostics via textDocument/publishDiagnostics
public class DiagnosticService
{
    private readonly TemplateEngine _templateEngine;
    private readonly ILanguageServerFacade _server;

    public void ValidateDocument(DocumentUri uri, string content)
    {
        var result = _templateEngine.Validate(content);
        var diagnostics = result.Errors
            .Select(e => new Diagnostic
            {
                Range = new Range(e.Line - 1, e.Column - 1, e.Line - 1, e.Column + 10),
                Severity = DiagnosticSeverity.Error,
                Code = e.Code,
                Message = e.Message,
                Source = "rtl"
            })
            .Concat(result.Warnings.Select(w => new Diagnostic
            {
                Range = new Range(w.Line - 1, w.Column - 1, w.Line - 1, w.Column + 10),
                Severity = DiagnosticSeverity.Warning,
                Code = w.Code,
                Message = w.Message,
                Source = "rtl"
            }))
            .ToList();

        _server.TextDocument.PublishDiagnostics(new PublishDiagnosticsParams
        {
            Uri = uri,
            Diagnostics = new Container<Diagnostic>(diagnostics)
        });
    }
}

4.6 Hover Provider (LSP Hover)

HoverHandler.cs:

  • Hover over style → shows description
  • Hover over binding → shows data type from schema
  • Hover over control flow → shows syntax help
  • Hover over format specifier → shows example output
public class HoverHandler : IHoverHandler
{
    public Task<Hover?> Handle(HoverParams request, CancellationToken ct)
    {
        var content = GetHoverContent(request.TextDocument.Uri, request.Position);
        if (content == null) return Task.FromResult<Hover?>(null);

        return Task.FromResult<Hover?>(new Hover
        {
            Contents = new MarkedStringsOrMarkupContent(new MarkupContent
            {
                Kind = MarkupKind.Markdown,
                Value = content
            })
        });
    }
}

4.7 Code Snippets (VS Code Extension)

snippets/rtl.snippets.json:

{
  "If Block": {
    "prefix": "@if",
    "body": ["@if ${1:condition}", "  $0", "@end"],
    "description": "Conditional block"
  },
  "Foreach Loop": {
    "prefix": "@foreach",
    "body": ["@foreach ${1:item} in ${2:collection}", "  $0", "@end"],
    "description": "Loop over collection"
  },
  "Row": {
    "prefix": "@row",
    "body": ["@row ${1:60}:${2:40}", "${3:left} | >${4:right}", "@endrow"],
    "description": "Two-column row"
  },
  "Styled Text": {
    "prefix": "#style",
    "body": ["#${1|bold,big,red,center,right|}# ${0:text}"],
    "description": "Styled text line"
  }
}

4.8 Preview Panel (Webview + Custom LSP Request)

The preview uses a custom LSP request to get rendered HTML from the language server.

Custom request (Language Server):

// CustomRequestHandlers.cs
[Method("rtl/renderPreview")]
public record RenderPreviewParams
{
    public DocumentUri Uri { get; init; }
    public string PrinterProfile { get; init; }  // "tm-t30iii" or "tm-u220ii"
    public string? SampleDataPath { get; init; }
}

public record RenderPreviewResult
{
    public string Html { get; init; }           // Rendered receipt as HTML
    public string? Error { get; init; }         // Render error message if any
}

public class CustomRequestHandlers : IJsonRpcRequestHandler<RenderPreviewParams, RenderPreviewResult>
{
    public Task<RenderPreviewResult> Handle(RenderPreviewParams request, CancellationToken ct)
    {
        // 1. Load template content
        // 2. Load sample data JSON
        // 3. Get printer profile
        // 4. Call TemplateEngine.Render()
        // 5. Convert PrintCommand[] to HTML
        // 6. Return HTML or error
    }
}

VS Code Extension (previewPanel.ts):

async function updatePreview() {
    const result = await client.sendRequest<RenderPreviewResult>('rtl/renderPreview', {
        uri: activeDocument.uri.toString(),
        printerProfile: selectedPrinter,
        sampleDataPath: selectedSamplePath
    });

    if (result.error) {
        panel.webview.html = renderError(result.error);
    } else {
        panel.webview.html = wrapInReceiptStyle(result.html);
    }
}

Features:

  • Side-by-side preview (like Markdown preview)
  • Printer profile selector (TM-T30III, TM-U220II)
  • Live updates as template changes (debounced 300ms)
  • Shows render errors inline

4.9 Sample Data Integration

Custom LSP requests:

// Get schema from sample JSON (for autocomplete)
[Method("rtl/getSchema")]
public record GetSchemaParams { public string SampleDataPath { get; init; } }
public record GetSchemaResult { public SchemaNode Schema { get; init; } }

// List available sample files
[Method("rtl/listSamples")]
public record ListSamplesResult { public List<string> SampleFiles { get; init; } }

Features:

  • Status bar item shows current sample file
  • Click status bar → quick pick to change sample
  • Sample JSON files in samples/ directory
  • Automatic schema extraction for autocomplete
  • Sample file association by naming convention

4.10 Tree Views (VS Code Extension)

Templates Tree View:

  • Lists all .template files
  • Context menu: Rename, Delete, Duplicate, New
  • Click to open

Samples Tree View:

  • Lists all .json sample files
  • Shows which template they're associated with
  • Click to open

Assignments Tree View:

  • Shows current assignments from assignments.json
  • Inline display: Receipt → final-receipt.template
  • Context menu: Edit, Delete
  • "Add Assignment" button

4.11 Assignments Editor (Webview)

Webview panel for editing assignments.json:

┌─────────────────────────────────────────────────────────────────────┐
│ Template Assignments                                       [Save]   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│ Receipt Type        Printer         Template              Actions   │
│ ─────────────────────────────────────────────────────────────────── │
│ [Receipt        ▼]  [(Any)      ▼]  [final-receipt    ▼]  [Delete]  │
│ [WorkareaTicket ▼]  [(Any)      ▼]  [kitchen-default  ▼]  [Delete]  │
│ [WorkareaTicket ▼]  [TM-U220II  ▼]  [kitchen-u220     ▼]  [Delete]  │
│ [NextCourse     ▼]  [(Any)      ▼]  [kitchen-default  ▼]  [Delete]  │
│ [OrdersOverview ▼]  [(Any)      ▼]  [orders-overview  ▼]  [Delete]  │
│                                                                      │
│ [+ Add Assignment]                                                   │
│                                                                      │
│ Fallback Template: [fallback.template              ▼]                │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘

5. Data Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│                              VS Code                                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                    vscode-rtl Extension (TypeScript)                   │ │
│  │                                                                        │ │
│  │  ┌──────────────┐    ┌──────────────┐    ┌──────────────────────────┐ │ │
│  │  │ Language     │    │ Preview      │    │ Assignments              │ │ │
│  │  │ Client       │    │ Webview      │    │ Webview                  │ │ │
│  │  └──────┬───────┘    └──────┬───────┘    └────────────┬─────────────┘ │ │
│  │         │                   │                         │               │ │
│  └─────────┼───────────────────┼─────────────────────────┼───────────────┘ │
│            │ LSP               │ Custom Request          │ File I/O        │
│            │ (JSON-RPC)        │ rtl/renderPreview       │                 │
│            ▼                   ▼                         ▼                 │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │           Language Server (C# - stdio process)                      │   │
│  │                                                                     │   │
│  │  ┌─────────────────────────────────────────────────────────────┐   │   │
│  │  │ Handlers                                                     │   │   │
│  │  │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │   │   │
│  │  │ │ Semantic   │ │ Completion │ │ Diagnostic │ │ Custom     │ │   │   │
│  │  │ │ Tokens     │ │ Handler    │ │ Handler    │ │ Requests   │ │   │   │
│  │  │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │   │   │
│  │  └───────┼──────────────┼──────────────┼──────────────┼────────┘   │   │
│  │          │              │              │              │            │   │
│  │          ▼              ▼              ▼              ▼            │   │
│  │  ┌─────────────────────────────────────────────────────────────┐   │   │
│  │  │ Services                                                     │   │   │
│  │  │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │   │   │
│  │  │ │ Document   │ │ Schema     │ │ Scope      │ │ Diagnostic │ │   │   │
│  │  │ │ Manager    │ │ Service    │ │ Tracker    │ │ Service    │ │   │   │
│  │  │ └────────────┘ └─────┬──────┘ └────────────┘ └─────┬──────┘ │   │   │
│  │  └──────────────────────┼─────────────────────────────┼────────┘   │   │
│  │                         │                             │            │   │
│  │                         ▼                             ▼            │   │
│  │  ┌─────────────────────────────────────────────────────────────┐   │   │
│  │  │ Inspectron.Epson.Templates (Phase 1 - Referenced)           │   │   │
│  │  │ ┌────────────┐ ┌────────────┐ ┌────────────┐                │   │   │
│  │  │ │ Lexer      │ │ Parser     │ │ Template   │                │   │   │
│  │  │ │            │ │            │ │ Engine     │                │   │   │
│  │  │ └────────────┘ └────────────┘ └────────────┘                │   │   │
│  │  └─────────────────────────────────────────────────────────────┘   │   │
│  │                                                                     │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Key points:

  • Language Server is a standalone C# console application communicating via stdio
  • All language features (highlighting, completion, validation) reuse Phase 1 code
  • Custom LSP requests handle preview rendering and schema extraction
  • VS Code extension is a thin client (~500 lines of TypeScript)

6. Language Server Implementation Details

6.1 Context Detection (CompletionService.cs)

public enum CompletionContext
{
    None,
    Style,             // Inside #...#
    ControlFlow,       // After @ at line start
    Binding,           // Inside {...}
    BindingProperty,   // After . inside {...}
    BindingFormat,     // After : inside {...}
    ForeachCollection, // After "in" in @foreach
    RowContent         // Inside @row...@endrow
}

public class CompletionService
{
    public CompletionContext DetectContext(string line, int column)
    {
        var textBefore = line[..column];

        // Check for binding context
        var lastOpenBrace = textBefore.LastIndexOf('{');
        var lastCloseBrace = textBefore.LastIndexOf('}');
        if (lastOpenBrace > lastCloseBrace)
        {
            var bindingText = textBefore[(lastOpenBrace + 1)..];
            if (bindingText.Contains(':')) return CompletionContext.BindingFormat;
            if (bindingText.Contains('.')) return CompletionContext.BindingProperty;
            return CompletionContext.Binding;
        }

        // Check for style context
        var hashCount = textBefore.Count(c => c == '#');
        if (hashCount % 2 == 1) return CompletionContext.Style;

        // Check for control flow
        if (Regex.IsMatch(textBefore, @"^\s*@\w*$"))
            return CompletionContext.ControlFlow;

        // Check for foreach collection
        if (Regex.IsMatch(textBefore, @"@foreach\s+\w+\s+in\s+\w*$"))
            return CompletionContext.ForeachCollection;

        return CompletionContext.None;
    }
}

6.2 Schema Extraction (SchemaService.cs)

public abstract record SchemaNode(string Name);
public record ObjectNode(string Name, List<SchemaNode> Properties) : SchemaNode(Name);
public record ArrayNode(string Name, SchemaNode? ItemType) : SchemaNode(Name);
public record ValueNode(string Name, JsonValueKind ValueKind) : SchemaNode(Name);

public class SchemaService
{
    private readonly Dictionary<string, SchemaNode> _schemaCache = new();

    public SchemaNode ExtractSchema(string jsonContent)
    {
        using var doc = JsonDocument.Parse(jsonContent);
        return AnalyzeElement("root", doc.RootElement);
    }

    private SchemaNode AnalyzeElement(string name, JsonElement element)
    {
        return element.ValueKind switch
        {
            JsonValueKind.Object => new ObjectNode(name,
                element.EnumerateObject()
                    .Select(p => AnalyzeElement(p.Name, p.Value))
                    .ToList()),

            JsonValueKind.Array => new ArrayNode(name,
                element.GetArrayLength() > 0
                    ? AnalyzeElement("item", element[0])
                    : null),

            _ => new ValueNode(name, element.ValueKind)
        };
    }

    public SchemaNode? ResolvePropertyPath(SchemaNode root, string path)
    {
        var parts = path.Split('.');
        var current = root;

        foreach (var part in parts)
        {
            current = current switch
            {
                ObjectNode obj => obj.Properties.FirstOrDefault(p => p.Name == part),
                ArrayNode arr => arr.ItemType,
                _ => null
            };
            if (current == null) return null;
        }

        return current;
    }
}

6.3 Scope Tracking (ScopeTracker.cs)

Track active @foreach loops to resolve loop variable bindings:

public record LoopScope(
    string VariableName,
    string CollectionPath,
    SchemaNode? ItemSchema,
    int StartLine,
    int EndLine);

public class ScopeTracker
{
    private readonly SchemaService _schemaService;

    public List<LoopScope> GetActiveScopes(string documentContent, int line, SchemaNode? rootSchema)
    {
        var lines = documentContent.Split('\n');
        var stack = new Stack<(string Variable, string Collection, int Line)>();
        var scopes = new List<LoopScope>();

        for (int i = 0; i <= line && i < lines.Length; i++)
        {
            var currentLine = lines[i];
            var foreachMatch = Regex.Match(currentLine, @"@foreach\s+(\w+)\s+in\s+(\S+)");

            if (foreachMatch.Success)
            {
                stack.Push((foreachMatch.Groups[1].Value, foreachMatch.Groups[2].Value, i));
            }
            else if (Regex.IsMatch(currentLine, @"@end\b") && stack.Count > 0)
            {
                // Only pop if this @end is before or at the cursor line
                if (i < line)
                {
                    stack.Pop();
                }
            }
        }

        // Convert stack to scopes with schema info
        foreach (var (variable, collection, startLine) in stack)
        {
            var collectionSchema = rootSchema != null
                ? _schemaService.ResolvePropertyPath(rootSchema, collection)
                : null;

            var itemSchema = collectionSchema is ArrayNode arr ? arr.ItemType : null;

            scopes.Add(new LoopScope(variable, collection, itemSchema, startLine, -1));
        }

        return scopes;
    }

    public List<CompletionItem> GetLoopVariableCompletions(List<LoopScope> scopes)
    {
        var items = new List<CompletionItem>();

        foreach (var scope in scopes)
        {
            // Add the loop variable itself
            items.Add(new CompletionItem
            {
                Label = scope.VariableName,
                Kind = CompletionItemKind.Variable,
                Detail = $"Loop variable from @foreach {scope.VariableName} in {scope.CollectionPath}"
            });

            // Add loop metadata
            items.AddRange(new[]
            {
                new CompletionItem { Label = $"{scope.VariableName}._index", Kind = CompletionItemKind.Property, Detail = "0-based index" },
                new CompletionItem { Label = $"{scope.VariableName}._number", Kind = CompletionItemKind.Property, Detail = "1-based number" },
                new CompletionItem { Label = $"{scope.VariableName}._first", Kind = CompletionItemKind.Property, Detail = "true if first iteration" },
                new CompletionItem { Label = $"{scope.VariableName}._last", Kind = CompletionItemKind.Property, Detail = "true if last iteration" }
            });

            // Add item properties if schema is available
            if (scope.ItemSchema is ObjectNode obj)
            {
                foreach (var prop in obj.Properties)
                {
                    items.Add(new CompletionItem
                    {
                        Label = $"{scope.VariableName}.{prop.Name}",
                        Kind = CompletionItemKind.Property,
                        Detail = GetPropertyTypeDescription(prop)
                    });
                }
            }
        }

        return items;
    }
}

6.4 Semantic Tokens (SemanticTokenService.cs)

public class SemanticTokenService
{
    // Token types must match SemanticTokensLegend sent during initialization
    private static readonly string[] TokenTypes = new[]
    {
        "keyword",    // 0: @if, @foreach, @end, @row
        "variable",   // 1: {binding}
        "property",   // 2: .property
        "decorator",  // 3: #style#
        "string",     // 4: style names
        "number",     // 5: ratios
        "operator",   // 6: |, >, ^, ==
        "comment",    // 7: separators
        "type"        // 8: format specifiers
    };

    public SemanticTokens GetSemanticTokens(string content)
    {
        var tokens = new List<int>(); // [deltaLine, deltaStart, length, tokenType, tokenModifiers]
        var lexer = new Lexer(content);
        var allTokens = lexer.Tokenize();

        int prevLine = 0;
        int prevChar = 0;

        foreach (var token in allTokens)
        {
            var tokenType = MapTokenType(token.Type);
            if (tokenType < 0) continue;

            var deltaLine = token.Line - 1 - prevLine;
            var deltaStart = deltaLine == 0 ? token.Column - 1 - prevChar : token.Column - 1;

            tokens.Add(deltaLine);
            tokens.Add(deltaStart);
            tokens.Add(token.Length);
            tokens.Add(tokenType);
            tokens.Add(0); // No modifiers

            prevLine = token.Line - 1;
            prevChar = token.Column - 1;
        }

        return new SemanticTokens { Data = tokens.ToImmutableArray() };
    }

    private int MapTokenType(TokenType type) => type switch
    {
        TokenType.If or TokenType.Foreach or TokenType.End or
        TokenType.Row or TokenType.EndRow => 0,          // keyword
        TokenType.Binding => 1,                           // variable
        TokenType.PropertyPath => 2,                      // property
        TokenType.StyleMarker => 3,                       // decorator
        TokenType.StyleName => 4,                         // string
        TokenType.Ratio => 5,                             // number
        TokenType.Operator or TokenType.ColumnDelimiter
        or TokenType.AlignmentMarker => 6,               // operator
        TokenType.Separator => 7,                         // comment
        TokenType.FormatSpecifier => 8,                   // type
        _ => -1                                           // skip
    };
}

7. Commands and Keybindings

Commands (package.json)

{
  "contributes": {
    "commands": [
      {
        "command": "rtl.openPreview",
        "title": "Open Preview",
        "category": "RTL",
        "icon": "$(open-preview)"
      },
      {
        "command": "rtl.openPreviewToSide",
        "title": "Open Preview to the Side",
        "category": "RTL",
        "icon": "$(open-preview)"
      },
      {
        "command": "rtl.refreshPreview",
        "title": "Refresh Preview",
        "category": "RTL"
      },
      {
        "command": "rtl.selectSampleData",
        "title": "Select Sample Data",
        "category": "RTL"
      },
      {
        "command": "rtl.editSampleData",
        "title": "Edit Sample Data",
        "category": "RTL"
      },
      {
        "command": "rtl.editAssignments",
        "title": "Edit Assignments",
        "category": "RTL"
      },
      {
        "command": "rtl.newTemplate",
        "title": "New Template",
        "category": "RTL",
        "icon": "$(new-file)"
      },
      {
        "command": "rtl.duplicateTemplate",
        "title": "Duplicate Template",
        "category": "RTL"
      },
      {
        "command": "rtl.restartServer",
        "title": "Restart Language Server",
        "category": "RTL"
      }
    ]
  }
}

Keybindings

Shortcut Command When
Ctrl+Shift+V rtl.openPreview editorLangId == rtl
Ctrl+K V rtl.openPreviewToSide editorLangId == rtl
Ctrl+Shift+R rtl.refreshPreview editorLangId == rtl

Note: Standard VS Code shortcuts work automatically:

  • Ctrl+S - Save
  • Ctrl+Space - Trigger autocomplete
  • F8 / Shift+F8 - Navigate problems
  • Ctrl+G - Go to line
  • Ctrl+F / Ctrl+H - Find / Replace

8. Configuration

Extension Settings (contributes.configuration)

{
  "contributes": {
    "configuration": {
      "title": "RTL - Receipt Template Language",
      "properties": {
        "rtl.templatesDirectory": {
          "type": "string",
          "default": "./templates",
          "description": "Path to templates directory"
        },
        "rtl.samplesDirectory": {
          "type": "string",
          "default": "./templates/samples",
          "description": "Path to sample data directory"
        },
        "rtl.defaultPrinterProfile": {
          "type": "string",
          "enum": ["tm-t30iii", "tm-u220ii"],
          "default": "tm-t30iii",
          "description": "Default printer profile for preview"
        },
        "rtl.preview.debounceMs": {
          "type": "number",
          "default": 300,
          "description": "Delay before updating preview after changes"
        },
        "rtl.server.path": {
          "type": "string",
          "default": "",
          "description": "Path to language server executable (uses bundled server if empty)"
        },
        "rtl.trace.server": {
          "type": "string",
          "enum": ["off", "messages", "verbose"],
          "default": "off",
          "description": "Traces communication between VS Code and the language server"
        }
      }
    }
  }
}

Workspace Settings Example

// .vscode/settings.json
{
  "rtl.templatesDirectory": "./templates",
  "rtl.samplesDirectory": "./templates/samples",
  "rtl.defaultPrinterProfile": "tm-t30iii"
}

9. Dependencies

9.1 Language Server (C#)

Inspectron.Epson.Templates.LanguageServer.csproj:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishSingleFile>true</PublishSingleFile>
    <SelfContained>true</SelfContained>
  </PropertyGroup>

  <ItemGroup>
    <!-- OmniSharp Language Server Protocol -->
    <PackageReference Include="OmniSharp.Extensions.LanguageServer" Version="0.19.*" />
    <PackageReference Include="OmniSharp.Extensions.LanguageProtocol" Version="0.19.*" />

    <!-- Project Reference to Phase 1 -->
    <ProjectReference Include="../Inspectron.Epson.Templates/Inspectron.Epson.Templates.csproj" />
  </ItemGroup>
</Project>

9.2 VS Code Extension (TypeScript)

package.json:

{
  "name": "vscode-rtl",
  "displayName": "RTL - Receipt Template Language",
  "description": "Syntax highlighting, autocomplete, and preview for Receipt Template Language",
  "version": "1.0.0",
  "publisher": "inspectron",
  "engines": {
    "vscode": "^1.85.0"
  },
  "categories": ["Programming Languages", "Formatters"],
  "activationEvents": [
    "onLanguage:rtl",
    "workspaceContains:**/*.template"
  ],
  "main": "./dist/extension.js",
  "dependencies": {
    "vscode-languageclient": "^9.0.0"
  },
  "devDependencies": {
    "@types/vscode": "^1.85.0",
    "@types/node": "^20.x",
    "typescript": "^5.3.0",
    "webpack": "^5.x",
    "webpack-cli": "^5.x",
    "ts-loader": "^9.x",
    "@vscode/test-electron": "^2.x"
  },
  "scripts": {
    "vscode:prepublish": "npm run package && npm run build-server",
    "compile": "webpack",
    "watch": "webpack --watch",
    "package": "webpack --mode production --devtool hidden-source-map",
    "build-server": "dotnet publish ../Inspectron.Epson.Templates.LanguageServer -c Release -o ./server",
    "test": "node ./out/test/runTest.js"
  }
}

10. Testing

10.1 Language Server Tests (C# - xUnit)

Inspectron.Epson.Templates.LanguageServer.Tests/
├── Handlers/
│   ├── SemanticTokensHandlerTests.cs
│   ├── CompletionHandlerTests.cs
│   ├── DiagnosticHandlerTests.cs
│   └── HoverHandlerTests.cs
├── Services/
│   ├── CompletionServiceTests.cs
│   ├── SchemaServiceTests.cs
│   ├── ScopeTrackerTests.cs
│   └── SemanticTokenServiceTests.cs
└── Fixtures/
    ├── valid-template.template
    ├── invalid-template.template
    └── sample-data.json

Test Cases

1. Semantic Tokens Tests

  • Tokenize style markers #bold,center# → decorator + string
  • Tokenize bindings {property.path} → variable + property
  • Tokenize control flow → keyword
  • Tokenize separators → comment
  • Tokenize row syntax @row 60:40 → keyword + number

2. Completion Tests

  • Complete styles after #
  • Complete properties after {
  • Complete child properties after .
  • Complete format specifiers after :
  • Complete loop variables in scope
  • Complete loop metadata (_index, _first, etc.)

3. Diagnostics Tests

  • Error on unclosed @foreach
  • Error on unclosed { binding
  • Warning on unused loop variable
  • Error on invalid style name

4. Schema Tests

  • Extract schema from nested JSON
  • Resolve property paths
  • Handle arrays correctly

5. Scope Tracker Tests

  • Track single @foreach scope
  • Track nested @foreach scopes
  • Close scope after @end

10.2 VS Code Extension Tests (TypeScript)

vscode-rtl/test/
├── extension.test.ts         # Extension activation tests
├── languageClient.test.ts    # LSP client connection tests
└── fixtures/
    └── test-workspace/
        ├── test.template
        └── test.json

Manual Test Checklist

  • Install extension in VS Code
  • Open .template file - syntax highlighting works (semantic tokens)
  • Type @ at line start - autocomplete shows control flow
  • Type { - autocomplete shows data properties
  • Type {item. inside foreach - autocomplete shows item properties + _index, _first, etc.
  • Add syntax error - red squiggle appears, Problems panel updates
  • Fix error - squiggle disappears
  • Hover over style - shows description
  • Hover over binding - shows type from schema
  • Open preview (Ctrl+Shift+V) - receipt preview appears
  • Edit template - preview updates live
  • Change printer in preview - width adjusts
  • Open assignments editor - table displays correctly
  • Add/edit/delete assignment - saves to JSON
  • Restart Language Server command works

11. Distribution

Build Process

# 1. Build language server for all platforms
dotnet publish Inspectron.Epson.Templates.LanguageServer -c Release -r win-x64 -o vscode-rtl/server/win-x64
dotnet publish Inspectron.Epson.Templates.LanguageServer -c Release -r linux-x64 -o vscode-rtl/server/linux-x64
dotnet publish Inspectron.Epson.Templates.LanguageServer -c Release -r osx-x64 -o vscode-rtl/server/osx-x64

# 2. Build VS Code extension
cd vscode-rtl
npm install
npm run package

# 3. Create VSIX
npx @vscode/vsce package

# Creates: vscode-rtl-1.0.0.vsix

VSIX Contents

vscode-rtl-1.0.0.vsix
├── extension/
│   ├── dist/
│   │   └── extension.js        # Bundled TypeScript
│   ├── server/
│   │   ├── win-x64/           # Windows language server
│   │   ├── linux-x64/         # Linux language server
│   │   └── osx-x64/           # macOS language server
│   ├── media/
│   ├── snippets/
│   ├── language-configuration.json
│   └── package.json
└── [Content_Types].xml

Installation Options

  1. Local VSIX install:

    code --install-extension vscode-rtl-1.0.0.vsix
    
  2. VS Code Marketplace: (optional, for public distribution)

    npx @vscode/vsce publish
    
  3. Manual installation:

    • Copy extracted folder to:
      • Windows: %USERPROFILE%\.vscode\extensions\
      • macOS/Linux: ~/.vscode/extensions/

12. Timeline

Phase 1: Core Engine (Foundation) - C#

  1. Project setup and structure
  2. Lexer implementation + tests
  3. Parser implementation + tests
  4. Interpreter implementation + tests
  5. Configuration and resolver
  6. File storage
  7. Factory integration
  8. Default templates
  9. Integration testing
  10. Migration and deployment

Phase 2: VS Code Extension + Language Server (After Phase 1 Complete)

Language Server (C#):

  1. Project setup with OmniSharp LSP
  2. Server initialization and stdio transport
  3. TextDocumentSyncHandler (open/change/close)
  4. SemanticTokensHandler (syntax highlighting)
  5. DiagnosticService + PublishDiagnostics
  6. FoldingRangeHandler
  7. HoverHandler
  8. CompletionHandler - styles and control flow
  9. SchemaService and ScopeTracker
  10. CompletionHandler - data bindings with scope awareness
  11. Custom request: rtl/renderPreview
  12. Custom request: rtl/getSchema, rtl/listSamples
  13. Unit tests

VS Code Extension (TypeScript): 14. Extension project setup (package.json, webpack) 15. Language client setup and server spawning 16. Language registration and configuration 17. Preview webview - calls rtl/renderPreview 18. Preview webview - printer selector, live updates 19. Tree views (templates, samples) - optional 20. Assignments editor webview - optional 21. Code snippets 22. Integration testing 23. Multi-platform build and VSIX packaging 24. Documentation and distribution