Editor Setup
Norq includes a Language Server Protocol (LSP) server that provides editor intelligence for .md and .json template files inside notification directories.
Features
| Feature | Description |
|---|---|
| Diagnostics | Real-time lint errors and warnings as you type |
| Completions | Variable names from data.schema.yaml, directive names, pipe names |
| Hover | Type info and descriptions from the schema on hover |
| Null safety | Warnings when nullable fields are used without :::if guards |
| Code actions | Quick fixes (e.g., wrap nullable access in :::if guard) |
VS Code
Install the Norq extension from the VS Code marketplace. It bundles the LSP server and provides:
- Syntax highlighting for
.mdtemplate files - LSP features (diagnostics, completions, hover, code actions)
- Inline preview panel
- Channel switcher in the status bar
The extension automatically detects projects with norq.config.yaml and activates.
Neovim
With native LSP (recommended)
vim.api.nvim_create_autocmd('FileType', {
pattern = 'markdown',
callback = function()
vim.lsp.start({
name = 'norq',
cmd = { 'norq', 'lsp' },
root_dir = vim.fs.root(0, 'norq.config.yaml'),
})
end,
})With nvim-lspconfig
local lspconfig = require("lspconfig")
lspconfig.norq.setup({
cmd = { "norq", "lsp" },
filetypes = { "markdown" },
root_dir = lspconfig.util.root_pattern("norq.config.yaml"),
})With coc.nvim
Add to coc-settings.json:
{
"languageserver": {
"norq": {
"command": "norq",
"args": ["lsp"],
"filetypes": ["markdown"],
"rootPatterns": ["norq.config.yaml"]
}
}
}Vim
Vim does not have a built-in LSP client. Use the vim-lsp plugin.
Add to your .vimrc:
if executable('norq')
au User lsp_setup call lsp#register_server({
\ 'name': 'norq',
\ 'cmd': {server_info->['norq', 'lsp']},
\ 'allowlist': ['markdown'],
\ 'root_uri': {server_info->lsp#utils#path_to_uri(
\ lsp#utils#find_nearest_parent_file_directory(
\ lsp#utils#get_buffer_path(), 'norq.config.yaml'))},
\ })
endifHelix
Add to languages.toml:
[[language]]
name = "markdown"
language-servers = ["norq-lsp"]
[language-server.norq-lsp]
command = "norq"
args = ["lsp"]Zed
Install the Norq extension from the Zed extension registry, or load the zed/ directory as a dev extension:
- Open Zed
- Go to Extensions > Install Dev Extension
- Select the
zed/directory in the norq repo
The extension automatically finds norq on your PATH and launches the LSP.
Sublime Text
Install the LSP package, then add to LSP settings:
{
"clients": {
"norq": {
"command": ["norq", "lsp"],
"selector": "text.html.markdown",
"initializationOptions": {},
"settings": {}
}
}
}Emacs (lsp-mode)
(with-eval-after-load 'lsp-mode
(add-to-list 'lsp-language-id-configuration '(markdown-mode . "markdown"))
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection '("norq" "lsp"))
:activation-fn (lsp-activate-on "markdown")
:server-id 'norq-lsp)))How the LSP works
The LSP server starts via norq lsp on stdin/stdout. It:
- Discovers the project root by finding
norq.config.yaml - Loads all
data.schema.yamlfiles for completion and type info - Watches for file changes to update diagnostics in real time
- Provides scoped completions based on the current context (directive names after
:::, variable paths inside{{, pipe names after|)
The LSP only activates for files inside a notifications/ directory that is part of a Norq project.
Completions
The LSP provides context-aware completions:
- After
:::-- directive names (header,footer,action,highlight,centered,table, etc.) - Inside
{{-- variable paths fromdata.schema.yamlwith dot-path traversal - After
|inside{{-- pipe names (capitalize,uppercase,truncate, etc.) - Inside frontmatter -- known frontmatter keys for the current channel
- After a space following a directive name -- directive parameters (
center,left,right,compact,spacious,bg=,color=)
Completion trigger characters
The LSP registers the following characters as completion triggers: :, {, |, ., (space).
Space triggers directive-parameter completions. After typing ::: hero , the LSP suggests layout params (center, left, right) and style params (bg=, color=, compact, spacious) relevant to that directive.
Directive completions
The following directives appear in completions after ::::
| Directive | Description |
|---|---|
header |
Branded page header |
footer |
Footer with small text |
action |
Call-to-action button(s) |
callout |
Highlighted callout box |
hero |
Full-width image or banner |
fields |
Key-value data table |
media |
Image, video, or file link |
columns |
Multi-column layout |
list |
Ordered or unordered list |
highlight |
Dark card with brand color background |
centered |
Center-aligned text block |
table |
Iterable data table (rows from array variable) |
if / else / for |
Control flow |
raw |
Raw passthrough block |
Directive parameter completions
After a space following a directive name, the LSP offers parameter completions:
- Alignment:
center,left,right - Density:
compact,spacious - Style overrides:
bg=,color=
Example: typing ::: highlight triggers suggestions for bg=, color=, compact, spacious.
Diagnostics
Diagnostics appear in real time as you edit:
- Error: Parse failures, missing required frontmatter
- Warning: Nullable access without
:::ifguard or| defaultpipe, raw expressions - Info: Native JSON mode suggestion, unused variables
Code Actions
The LSP offers quick fixes triggered by diagnostics.
Wrap in :::if guard
When the linter warns about nullable access (template/nullable-access), the LSP offers a one-click fix to wrap the expression in an :::if guard. You can also silence the warning by using a | default pipe instead.
Before:
Hello {{user.last_name}}Diagnostic: Variable 'user.last_name' is nullable -- wrap in :::if guard or use | default "..."
Fix with code action (wraps in :::if guard):
:::if user.last_name
Hello {{user.last_name}}
:::Fix with | default pipe:
Hello {{user.last_name | default "friend"}}In VS Code, click the lightbulb or press Ctrl+. / Cmd+. on the warning to apply.
Config file support
The LSP provides intelligence for norq.config.yaml in addition to template files:
- Diagnostics -- unknown keys, missing required fields, invalid channel names in
routing - Completions -- top-level keys (
notifications,providers,routing,theme,codegen), theme sub-keys, channel names in routing values - Hover -- descriptions and default values for each config key
The config schema is defined in crates/core/src/schema/config.rs. Adding a new config key means adding one ConfigField entry.