Devicetree Language Server
npm install devicetree-language-serverThis LSP is intended to be used with DTS Devicetree Specification Release v0.4 (https://devicetree.org)
- Features
- Go to Definition
- Go to Declarations
- Go to References
- Hover
- Formatting
- Semantic Tokens
- Document Symbols
- Workspace Symbols
- Diagnostics
- Completions
- Refactoring
- Code Actions
- Installation
- Usage
- Zephyr
- Linux
- Devicetree-Org
- Sample Editor configurations
- kakoune
- Neovim - lazygit
- Helix
- coc.nvim
- Vim 9 - yegappan lsp
On node name/label reference; will list all the places where the node is altered. /delete-node/ cases are not listed.
On property name; will list all the places where the property is assigned a value. Note: defining a property name with no assign (empty) is equal to assigning a truthful value and hence it will also be shown.
NOTE: If for example a node with name node1 has been created, then deleted, and then created again, depending on where the definition call is made in the file, in one case one will get the definition from before the delete keyword, and in the other case the definition from under the delete keyword.
#### Zephyr - DT_MACROS
You can also use Go to Definition on a selected number of DT\_ APIs found in zephyr and get Hovers to help explain Node state or even DT_MACRO result for the current active Devicetree context
On node name/label reference; will list the first places where the node is created.
On property name; will list the first places where the property is assigned a value for the first time. Note: defining a property name with no assign (empty) is equal to assigning a truthful value and hence it will also be shown.
NOTE: The declarations will stop at the definition, hence, if for example a node with name node1 has been created, then deleted, and then created again, depending on where the declarations call is made in the file, in one case one will get the declarations from before the delete keyword up to the delete keyword, and in the other case from the delete keyword (excluded) onwards.
#### Zephyr - DT_MACROS
You can also use Go to Declarations on a selected number of DT\_ APIs found in zephyr and get Hovers to help explain Node state or even DT_MACRO result for the current active Devicetree context
- On node name/label reference; will list all the places where the node is used by name, label or in some path.
- On property name; will list all the places where the property referred to including /delete-property/.
NOTE: The references will stop at the definition, hence, if for example a node with name node1 has been created, then deleted, and then created again, depending on where the reference call is made in the file, in one case one will get the ones from before the delete keyword up to the delete keyword, and in the other case from the delete keyword (excluded) onwards.
On hover over the node name, a tooltip will show the final state of that node. If bindings are used it will also include the description from the binding files.
When hovering over a deleted state you can see the state of the item just before the delete action.
#### Zephyr - DT_MACROS
You can also hover on a selected number of DT\_ APIs found in zephyr and get Hovers to help explain Node state or even DT_MACRO result for the current active Devicetree context
This LSP follows the Zephyr Style Guide and is used in CI to validate all files upstream.
Every element in the document will have semantic tokens to help highlight and color code the items.
Every element in the document will have document symbols to help navigate the document in a tree format.
You can also navigate the active context using workspace symbols.
#### Reports generic syntax issues such as missing "," , "}" , ">" etc...
#### Reports when property has been replaced by a later definition and provides document link to where it has been redefined
#### Reports when node has been deleted and provides document link to where the delete was done
#### Reports when property has been deleted and provides document link to where it has been redefined
#### Reports label conflicts
#### Duplicate node name in the same node
#### Reports when deleting a node/property that does not exist
#### Reports CPreprocessor issues such as missing macro, invalid argument count etc.
#### Compares the node address and ensures that it matches the reg property, and that the reg values use the appropriate number of values as defined #address-cells
#### Reports property type mismatch errors
#### Reports prop-encoded-values errors when these need to follow some expected pattern e.g interrupts/nexus
#### Bus type validation when using Zephyr bindings
Completions are context aware of the document state on the line the action is requested.
#### Node path completion
#### Label reference completion reference node creation and property assignment
#### Delete Node:
Suggests appropriate type e.g. by reference or node name.
Does not suggest keyword if no delete is possible.
#### Delete Property:
Suggests property names available in that context.
Does not suggest keyword if no delete is possible.
#### Default values for standard types
#### Zephyr DT_MACRO Completion
Refactoring is possible on the following elements:
- Node names
- Node labels
- Property names
Given that in some cases the files included in a devicetree might come from an SDK which should not be edited, one can configure "lockRenameEdits" in the settings to lock refactoring from being permitted on any elements which would otherwise effect edits to the files listed in "lockRenameEdits".
- Adds missing syntax e.g. ';', '<', '>', ',' etc...
- Removes syntactically incorrect spaces:
- Between node name, '@' and address
- In node path reference
- Removes ';' when used without any statement
- Supports SourceFixAll/QuickFixes
Contributions are welcome or reach out on GitHub with requests.
The language server can be found on https://www.npmjs.com/package/devicetree-language-server.
To install runnpm i -g devicetree-language-server
The LSP has only been tested with Node 20.
This extension needs a client that supports Configuration Capability. The format for the configuration setting is of the type Settings as shown below:
At the moment, this LSP only supports bindings for the Zephyr project and has experimental support for Devicetree-Org Bindings.
``typescript
interface Context {
ctxName?: string | number;
cwd?: string;
includePaths?: string[];
dtsFile: string;
overlays?: string[];
bindingType?: BindingType;
zephyrBindings?: string[];
deviceOrgTreeBindings?: string[];
deviceOrgBindingsMetaSchema?: string[];
lockRenameEdits?: string[];
formattingErrorAsDiagnostics?: boolean;
compileCommands?: string;
}
interface Settings {
cwd?: string;
defaultBindingType?: BindingType;
defaultZephyrBindings?: string[];
defaultDeviceOrgTreeBindings?: string[];
defaultDeviceOrgBindingsMetaSchema?: string[];
defaultIncludePaths?: string[];
contexts?: Context[];
preferredContext?: string | number;
defaultLockRenameEdits?: string[];
defaultShowFormattingErrorAsDiagnostics?: boolean;
autoChangeContext?: boolean;
allowAdhocContexts?: boolean;
}
`
`json`
{
"devicetree.cwd": "/User/workspace/zephyr",
"devicetree.defaultIncludePaths": [
"./zephyr/dts",
"./zephyr/dts/arm",
"./zephyr/dts/arm64/",
"./zephyr/dts/riscv",
"./zephyr/dts/common",
"./zephyr/dts/vendor",
"./zephyr/include",
"./zephyr/dts/xtensa"
],
"devicetree.defaultBindingType": "Zephyr",
"devicetree.defaultZephyrBindings": ["./zephyr/dts/bindings"],
"devicetree.contexts": [
{
"devicetree.cwd": "/opt/nordic/ncs/v3.0.0",
"bindingType": "Zephyr",
"zephyrBindings": ["./zephyr/dts/bindings", "./nrf/dts/bindings"],
"includePaths": [
"./zephyr/dts",
"./zephyr/dts/arm",
"./zephyr/dts/arm64/",
"./zephyr/dts/riscv",
"./zephyr/dts/common",
"./zephyr/dts/vendor",
"./zephyr/include",
"./zephyr/dts/xtensa"
],
"dtsFile": "./zephyr/boards/nordic/nrf52840dk/nrf52840dk_nrf52840.dts",
"overlays": ["/User/project/myOverlay.overlay"]
}
]
}
`json`
{
"devicetree.cwd": "/Users/user/Workspace/linux/",
"devicetree.defaultIncludePaths": ["include"],
"devicetree.defaultBindingType": "DevicetreeOrg",
"devicetree.defaultDeviceOrgBindingsMetaSchema": [],
"devicetree.defaultDeviceOrgTreeBindings": []
}
#### With Devicetree-Org Bindings
`json`
{
"devicetree.cwd": "/Users/user/Workspace/linux/",
"devicetree.defaultIncludePaths": ["include"],
"devicetree.defaultBindingType": "DevicetreeOrg",
"devicetree.defaultDeviceOrgBindingsMetaSchema": [
"/Users/user/Workspace/linuxBindings/dt-schema/dtschema/meta-schemas" // https://github.com/devicetree-org/dt-schema/tree/main/dtschema/meta-schemas
],
"devicetree.defaultDeviceOrgTreeBindings": [
"/Users/user/Workspace/linuxBindings/dt-schema/dtschema/schemas", // https://github.com/devicetree-org/dt-schema/tree/main/dtschema/schemas
"/Users/user/Workspace/linux/Documentation/devicetree/bindings" // https://github.com/torvalds/linux/tree/master/Documentation/devicetree/bindings
]
}
#### Note
Devicetree-Org bindings are experimental.
#### kakoune
Sample configuration in for kakoune with Zephyr 3.7.99 or later.
Contribution by topisani
`
hook -group lsp-project-zephyr global BufSetOption filetype=(devicetree) %{
eval %sh{
root=$(eval "$kak_opt_lsp_find_root" .build_info.yml $(: kak_buffile))
[ -e "$root/.build_info.yml" ] || exit 0
settings=$(cat ".build_info.yml" | yq -c '{ "defaultIncludePaths": .cmake.devicetree."include-dirs", "contexts": [{ "bindingType": "Zephyr", "zephyrBindings": .cmake.devicetree."bindings-dirs", "includePaths": .cmake.devicetree."include-dirs", "dtsFile": .cmake.devicetree.files[0], "overlays": .cmake.devicetree.files[1:] }], "preferredContext": 0 } | { devicetree: {settings: {"_": {devicetree: .}}}}' | yj -jt | sed '/^\[devicetree\]$/d')
cat <
[devicetree]
root = '$root'
command = "npm"
args = ["x", "--", "devicetree-language-server", "--stdio"]
# command = "node"
# args = ["/home/topisani/git/dts-lsp/server/dist/server.js", "--stdio"]
settings_section = "_"
$settings
}
EOF
}
}
`
#### Neovim - lazygit
Example setup using lazygit
`lua
return {
"neovim/nvim-lspconfig",
opts = function(_, opts)
-- Don't let Mason try to handle this custom server
opts.servers.devicetree_ls = nil
local lspconfig = require("lspconfig")
local configs = require("lspconfig.configs")
local capabilities = vim.lsp.protocol.make_client_capabilities()
-- Enable semantic tokens
capabilities.textDocument = capabilities.textDocument or {}
capabilities.textDocument.semanticTokens = {
dynamicRegistration = false,
requests = {
range = false,
full = true,
},
tokenTypes = {
"namespace", "class", "enum", "interface", "struct", "typeParameter", "type",
"parameter", "variable", "property", "enumMember", "decorator", "event", "function",
"method", "macro", "label", "comment", "string", "keyword", "number", "regexp", "operator",
},
tokenModifiers = {
"declaration", "definition", "readonly", "static", "deprecated", "abstract",
"async", "modification", "documentation", "defaultLibrary",
},
formats = {'relative'}
}
-- Enable formatting
capabilities.textDocument.formatting = {
dynamicRegistration = false
}
-- Enable folding range support
capabilities.textDocument.foldingRange = {
dynamicRegistration = false,
lineFoldingOnly = true,
}
if not configs.devicetree_ls then
configs.devicetree_ls = {
default_config = {
cmd = { "devicetree-language-server", "--stdio" },
filetypes = { "dts", "dtsi" },
root_dir = lspconfig.util.root_pattern("zephyr", ".git", "."),
settings = {
devicetree = {
defaultIncludePaths = {
"./zephyr/dts",
"./zephyr/dts/arm",
"./zephyr/dts/arm64/",
"./zephyr/dts/riscv",
"./zephyr/dts/common",
"./zephyr/dts/vendor",
"./zephyr/include"
},
cwd = "${workspaceFolder}",
defaultBindingType = "Zephyr",
defaultZephyrBindings = {
"./zephyr/dts/bindings"
},
autoChangeContext = true,
allowAdhocContexts = true,
contexts = {},
},
},
capabilities = capabilities,
},
}
end
vim.notify("Custom devicetree_ls LSP loaded with semantic tokens & folding")
-- Setup the LSP
lspconfig.devicetree_ls.setup({
capabilities = capabilities,
})
end,
}
`
#### Helix
Contribution by bextract
`
[[language]]
name = "devicetree"
language-servers = ["devicetree_ls"]
[language-server.devicetree_ls]
command = "devicetree-language-server"
args = ["--stdio"]
config = { devicetree = { cwd = "/home/bex/zephyrproject/", defaultIncludePaths = ["./zephyr/dts","./zephyr/dts/arm","./zephyr/dts/arm64","./zephyr/dts/riscv","./zephyr/dts/common","./zephyr/dts/vendor","./zephyr/include","./zephyr/dts/xtensa"], defaultBindingType = "Zephyr", defaultZephyrBindings = ["./zephyr/dts/bindings"], contexts = [] } }
`
#### coco nvim
``
{
"suggest.completionItemKindLabels": {
"keyword": "",
"variable": "",
"value": "",
"operator": "Ψ",
"constructor": "",
"function": "ƒ",
"reference": "渚",
"constant": "",
"method": "",
"struct": "פּ",
"class": "",
"interface": "",
"text": "",
"enum": "",
"enumMember": "",
"module": "",
"color": "",
"property": "",
"field": "料",
"unit": "",
"event": "鬒",
"file": "",
"folder": "",
"snippet": "",
"typeParameter": "",
"default": ""
},
"diagnostic.errorSign": "❗",
"diagnostic.warningSign": "💡",
"diagnostic.infoSign": "💡",
"diagnostic.hintSign": "💡",
"diagnostic.signPriority": 100,
"languageserver": {
"devicetree_ls": {
"command": "devicetree-language-server",
"args": ["--stdio"],
"filetypes": ["dts", "dtsi"],
"rootPatterns": [".git"],
"initializationOptions": {
"AutomaticWorkspaceInit": true
},
"settings": {
"devicetree": {
"cwd": "/home/user/Workspace/Kernel/linux",
"defaultIncludePaths": ["./include"],
"defaultBindingType": "DevicetreeOrg",
"contexts": []
}
}
}
}
}
#### Vim 9 - yegappan lsp
Contribution by jclsn
`
set encoding=utf-8
set nocompatible
colorscheme habamax
call plug#begin('$MYVIMDIR/plugged')
Plug 'yegappan/lsp'
call plug#end()
let lspOpts = #{
\ aleSupport: v:false,
\ autoComplete: v:true,
\ autoHighlight: v:false,
\ autoHighlightDiags: v:true,
\ autoPopulateDiags: v:false,
\ completionMatcher: 'case',
\ completionMatcherValue: 1,
\ diagSignErrorText: '❗',
\ diagSignHintText: '💡',
\ diagSignInfoText: '💡',
\ diagSignWarningText: '💡',
\ diagSignPriority: {
\ 'Error': 100,
\ 'Warning': 99,
\ 'Information': 98,
\ 'Hint': 97
\ },
\ echoSignature: v:false,
\ hideDisabledCodeActions: v:false,
\ highlightDiagInline: v:true,
\ hoverInPreview: v:false,
\ ignoreMissingServer: v:false,
\ keepFocusInDiags: v:true,
\ keepFocusInReferences: v:true,
\ completionTextEdit: v:true,
\ diagVirtualTextAlign: 'above',
\ diagVirtualTextWrap: 'default',
\ noNewlineInCompletion: v:false,
\ omniComplete: v:null,
\ omniCompleteAllowBare: v:false,
\ outlineOnRight: v:false,
\ outlineWinSize: 20,
\ popupBorder: v:true,
\ popupBorderHighlight: 'Title',
\ popupBorderHighlightPeek: 'Special',
\ popupBorderSignatureHelp: v:false,
\ popupHighlightSignatureHelp: 'Pmenu',
\ popupHighlight: 'Normal',
\ semanticHighlight: v:true,
\ showDiagInBalloon: v:true,
\ showDiagInPopup: v:true,
\ showDiagOnStatusLine: v:false,
\ showDiagWithSign: v:true,
\ showDiagWithVirtualText: v:false,
\ showInlayHints: v:false,
\ showSignature: v:true,
\ snippetSupport: v:false,
\ ultisnipsSupport: v:false,
\ useBufferCompletion: v:false,
\ usePopupInCodeAction: v:false,
\ useQuickfixForLocations: v:false,
\ vsnipSupport: v:false,
\ bufferCompletionTimeout: 100,
\ customCompletionKinds: v:false,
\ completionKinds: {},
\ filterCompletionDuplicates: v:false,
\ condensedCompletionMenu: v:false,
\ }
autocmd User LspSetup call LspOptionsSet(lspOpts)
let lspServers = [#{
\ name: 'devicetree_ls',
\ filetype: ['dts', 'dtsi'],
\ path: 'devicetree-language-server',
\ args: ['--stdio'],
\
\ root_uri: {server_info -> lsp#utils#path_to_uri(
\ lsp#utils#find_nearest_parent_file_directory(expand('%:p'), '.git', '.')
\ )
\ },
\
\ initializationOptions: #{
\ settings: #{
\ devicetree: #{
\ cwd: getcwd(),
\ defaultIncludePaths: [
\ './include',
\ ],
\ defaultBindingType: 'DevicetreeOrg',
\ contexts: []
\ }
\ }
\ }
\ }]
autocmd User LspSetup call LspAddServer(lspServers)
" Remap leader key to space
nnoremap
let mapleader = " "
let maplocalleader = "-"
nnoremap
nnoremap
nnoremap
nnoremap
nnoremap
nnoremap
nnoremap
nnoremap
nnoremap
function! s:SmartHover() abort
let result = execute('LspHover')
if result =~ 'Error'
call feedkeys('K', 'n')
endif
endfunction
nnoremap
nnoremap
nnoremap
nnoremap
nnoremap
command! -nargs=0 -bar -range=% Format
set formatexpr=lsp#lsp#FormatExpr() " Map LspFormat to the gq command
``