Skip to main content
Language packs extend Fresh with support for new programming languages. They provide syntax highlighting, language-specific configuration, and LSP integration.

Quick Start

Scaffold a new language pack using the Fresh CLI:
fresh --init language
This creates a complete package structure:
my-language/
├── package.json          # Package manifest
├── grammars/
│   └── syntax.sublime-syntax  # Sublime syntax grammar (YAML)
├── validate.sh           # Validation script
└── README.md

Package Structure

Package Manifest

The package.json configures all aspects of your language pack:
{
  "$schema": "https://raw.githubusercontent.com/sinelaw/fresh/main/crates/fresh-editor/plugins/schemas/package.schema.json",
  "name": "my-language",
  "version": "0.1.0",
  "description": "Language support for MyLang",
  "type": "language",
  "author": "Your Name",
  "license": "MIT",
  "fresh": {
    "grammar": {
      "file": "grammars/syntax.sublime-syntax",
      "extensions": ["mylang", "ml"]
    },
    "language": {
      "commentPrefix": "//",
      "blockCommentStart": "/*",
      "blockCommentEnd": "*/",
      "tabSize": 4,
      "autoIndent": true
    },
    "lsp": {
      "command": "my-language-server",
      "args": ["--stdio"],
      "autoStart": true
    }
  }
}
The $schema field enables validation and autocomplete in editors that support JSON Schema.

Grammar Configuration

The grammar section tells Fresh how to syntax-highlight your language:
FieldTypeDescription
filestringPath to grammar file (relative to package root)
extensionsstring[]File extensions without dots (e.g., ["py", "pyw"])
firstLinestringOptional regex for shebang detection (e.g., "^#!.*python")
Use ["py"] not [".py"] - extensions should NOT include the dot.

Language Configuration

The language section configures editor behavior for your language:
FieldTypeDescription
commentPrefixstringLine comment prefix (e.g., //, #, --)
blockCommentStartstringBlock comment opening (e.g., /*, <!--)
blockCommentEndstringBlock comment closing (e.g., */, -->)
tabSizenumberDefault indentation width (e.g., 4, 2)
useTabsbooleanUse tabs instead of spaces for indentation
autoIndentbooleanEnable automatic indentation
formatter.commandstringFormatter executable (e.g., prettier, rustfmt)
formatter.argsstring[]Arguments for formatter (file path added automatically)

Formatter Examples

"formatter": {
  "command": "prettier",
  "args": ["--write"]
}
The file path is automatically appended to args. Some formatters expect stdin (use "-" as arg), others expect a file path.

LSP Integration

The lsp section configures Language Server Protocol support:
FieldTypeDescription
commandstringLSP server executable name or path
argsstring[]Command-line arguments (e.g., ["--stdio"])
autoStartbooleanStart server automatically when opening matching files
initializationOptionsobjectCustom LSP initialization options (language-specific)

Finding LSP Servers

Official LSP Registry

Microsoft’s official list of LSP implementations

langserver.org

Community-maintained directory of language servers

Common LSP Servers

LanguageServerCommandInstallation
Rustrust-analyzerrust-analyzerrustup component add rust-analyzer
TypeScript/JavaScripttypescript-language-servertypescript-language-servernpm install -g typescript-language-server
Pythonpyrightpyright-langservernpm install -g pyright
Gogoplsgoplsgo install golang.org/x/tools/gopls@latest
C/C++clangdclangdSystem package manager
Rubysolargraphsolargraphgem install solargraph
JavajdtlsjdtlsEclipse JDT LS

Advanced LSP Configuration

Some language servers accept custom initialization options:
"lsp": {
  "command": "rust-analyzer",
  "args": [],
  "autoStart": true,
  "initializationOptions": {
    "cargo": {
      "buildScripts": {
        "enable": true
      }
    },
    "procMacro": {
      "enable": true
    }
  }
}
Check your language server’s documentation for available initialization options. These vary by server.

Grammar Development

Fresh uses Sublime Text’s .sublime-syntax format (YAML-based) for syntax highlighting.

Finding Existing Grammars

Before writing a grammar from scratch, search for existing ones:
1

Search GitHub

Look for <language> sublime-syntax or <language> tmLanguage
2

Check VS Code Extensions

Many VS Code extensions use TextMate or Sublime grammars that you can adapt
3

Browse Package Control

Visit packagecontrol.io for Sublime Text packages

Grammar Compatibility

Fresh supports a subset of sublime-syntax features. Not all grammars will work.
Will NOT work:
  • Grammars using extends: Packages/... directive (grammar inheritance)
  • References to external grammars or packages
  • Dependencies on other grammar files
Will work:
  • Standalone, self-contained grammars
  • Grammars using only include for internal contexts
  • No external dependencies
Compatible examples:
  • See fresh-plugins/languages for working examples (templ, hare, solidity)
  • Standalone grammars from Package Control that don’t use extends

Testing Compatibility

Install your language pack locally (see Testing) and check logs:
tail -f ~/.local/state/fresh/logs/fresh-*.log
Look for Failed to parse grammar errors.

Attribution Requirements

When using an existing grammar:
1

Check the License

Ensure it allows redistribution (MIT, Apache, BSD are common)
2

Include License File

Copy the license to grammars/LICENSE
3

Credit Original Author

Add attribution to your README and package description
Example attribution:
## Grammar Attribution

The syntax grammar is derived from [original-package](https://github.com/user/repo)
by Original Author, licensed under MIT. See `grammars/LICENSE` for details.

Writing Grammars from Scratch

Recommendation: Start with an existing grammar from fresh-plugins/languages and adapt it, rather than writing from scratch.

Minimal Example

%YAML 1.2
---
name: My Language
scope: source.mylang
file_extensions: [mylang, ml]

contexts:
  main:
    # Line comments
    - match: //.*$
      scope: comment.line

    # Strings
    - match: '"'
      scope: string.quoted.double
      push:
        - match: '"'
          pop: true
        - match: \\.
          scope: constant.character.escape

    # Keywords
    - match: \b(if|else|while|for|return)\b
      scope: keyword.control

Documentation Resources

Sublime Syntax Reference

Complete format specification

Scope Naming Guide

Standard scope names for syntax elements

TextMate Grammars

Additional background information

Working Examples

Real grammars from fresh-plugins

Complete Working Example

From the Templ language pack:
%YAML 1.2
---
name: Templ
scope: source.templ
version: 2

file_extensions:
  - templ

variables:
  ident: '[a-zA-Z_][a-zA-Z0-9_]*'

contexts:
  main:
    # Templ component declaration
    - match: '\b(templ)\s+({{ident}})'
      captures:
        1: keyword.declaration.templ
        2: entity.name.function.templ
      push: component_params

    # Go code blocks
    - match: '\{%'
      scope: punctuation.section.embedded.begin.templ
      push: go_code

  component_params:
    - match: '\('
      scope: punctuation.section.parens.begin
      set: param_list

  param_list:
    - match: '\)'
      scope: punctuation.section.parens.end
      pop: true
    - match: '{{ident}}'
      scope: variable.parameter.templ

  go_code:
    - match: '%\}'
      scope: punctuation.section.embedded.end.templ
      pop: true
    # Include Go syntax rules here...

Testing and Local Development

The fastest way to test during development:
1

Open Fresh

Open Fresh with a test file for your language
2

Open Command Palette

Press Ctrl+P then type >
3

Install from Local Path

Type package and select “Package: Install from URL”Enter the full path to your language pack directory:
/path/to/your-language-pack
4

Check for Errors

Open command palette and run “Show Warnings”Look for grammar parse errors or missing files
5

Iterate

Edit your grammar, then reinstall from the same local path to reload

Alternative: Manual Installation

1

Copy Package

cp -r your-language-pack ~/.config/fresh/grammars/my-language/
2

Validate Manifest

cd your-language-pack
./validate.sh
3

Restart Fresh

Restart Fresh to load the new grammar

Validation

Always validate before publishing:
# Validate package.json schema
./validate.sh

# Test by installing locally
fresh  # Open Fresh and install from local path

# Check logs for errors
tail -f ~/.local/state/fresh/logs/fresh-*.log

Troubleshooting

Debugging Commands

# Show log locations
fresh --show-paths

# View Fresh logs (check for grammar parse errors)
tail -f ~/.local/state/fresh/logs/fresh-*.log

# Check LSP logs
tail -f ~/.local/state/fresh/logs/lsp/<language>-*.log

# Validate package.json
./validate.sh

Common Issues

Possible causes:
  1. Grammar uses extends directive - Most common issue. Fresh doesn’t support grammar inheritance.
    • Check logs for Failed to parse grammar
    • Find a standalone grammar or manually merge the base grammar
  2. Wrong file extension format - Use ["py"] not [".py"]
    "extensions": ["py", "pyw"]  // ✓ Correct
    "extensions": [".py", ".pyw"]  // ✗ Wrong
    
  3. Incorrect grammar file path - Check that the path in package.json matches the actual file location
    "file": "grammars/syntax.sublime-syntax"  // Must match actual path
    
Debugging steps:
  1. Verify server is installed:
    which rust-analyzer  # or your server command
    
  2. Check LSP logs:
    tail -f ~/.local/state/fresh/logs/lsp/<language>-*.log
    
  3. Test server manually:
    rust-analyzer --version
    
  4. Check LSP registry - Verify correct command and args at microsoft.github.io/language-server-protocol
Debugging steps:
  1. Verify formatter is installed:
    which prettier  # or your formatter
    
  2. Test formatter manually:
    prettier --write test.js
    
  3. Check formatter documentation - Ensure you’re using correct arguments
    • Some formatters need stdin: ["-"]
    • Others need file path: ["--write"] (path added automatically)
  4. Check Fresh logs for formatter errors
Common validation errors:
  1. Invalid JSON - Use a JSON validator or editor with schema support
  2. Missing required fields:
    {
      "name": "required",
      "version": "required",
      "type": "language"  // must be "language" for language packs
    }
    
  3. Invalid schema URL - Copy the exact URL from working examples

Publishing

Once your language pack is tested and working:
1

Push to Git Repository

Create a public Git repository (GitHub, GitLab, etc.) and push your package
2

Submit to Registry

Submit a PR to fresh-plugins-registryAdd your package to languages.json:
{
  "name": "my-language",
  "description": "Language support for MyLang",
  "repository": "https://github.com/username/fresh-mylang",
  "version": "0.1.0"
}
3

Wait for Approval

Maintainers will review your submission. Once merged, users can install via the command palette.

User Installation

After your package is in the registry, users can install it:
1

Open Command Palette

Press Ctrl+P then type >
2

Select Install Command

Type package and select “Package: Install from URL”
3

Enter Package Name

Type your package name or Git URL:
my-language
or
https://github.com/username/fresh-mylang

Examples

Solidity

Minimal example - just grammar and basic config

Templ

Complete self-contained grammar with no external dependencies

Hare

Systems language with LSP integration

Fresh Plugins Registry

Browse all published language packs

Next Steps

Plugin Development

Learn how to build plugins with custom functionality

Sublime Syntax Docs

Master the grammar format

LSP Specification

Understand the Language Server Protocol

Example Grammars

Study working language packs

Build docs developers (and LLMs) love