Skip to content

Commit

Permalink
Feat: Add definition on hover for variables defined by BitBake
Browse files Browse the repository at this point in the history
  • Loading branch information
idillon-sfl committed Sep 25, 2023
1 parent 6170deb commit 5b8941f
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 2 deletions.
5 changes: 5 additions & 0 deletions client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ The go to definition feature currently behaves as follows:
| class or inc-file | file |
| recipe | recipe definition and all bbappends |
| symbol | all symbols within the include hierarchy |

### Show definitions of BitBake's defined variables on hover
*This functionnality requires to [provide the BitBake's folder](#set-bitbakes-path)*

Place your cursor over a variable. If it is a BitBake defined variable, then its definition from the documentation will be displayed.
79 changes: 79 additions & 0 deletions server/src/BitBakeDocScanner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import path from 'path'
import fs from 'fs'

type SuffixType = 'layer' | 'providedItem' | undefined

export interface VariableInfos {
name: string
definition: string
validFiles?: RegExp[] // Files on which the variable is defined. If undefined, the variable is defined in all files.
suffixType?: SuffixType
}

type VariableInfosOverride = Partial<VariableInfos>

// Infos that can't be parsed properly from the doc
const variableInfosOverrides: Record<string, VariableInfosOverride> = {
BBFILE_PATTERN: {
suffixType: 'layer'
},
LAYERDEPENDS: {
suffixType: 'layer'
},
LAYERDIR: {
validFiles: [/^.*\/conf\/layer.conf$/]
},
LAYERDIR_RE: {
validFiles: [/^.*\/conf\/layer.conf$/]
},
LAYERVERSION: {
suffixType: 'layer'
},
PREFERRED_PROVIDER: {
suffixType: 'providedItem'
}
}

const variablesFolder = 'doc/bitbake-user-manual/bitbake-user-manual-ref-variables.rst'
const variablesRegexForDoc = /^ {3}:term:`(?<name>[A-Z_]*?)`\n(?<definition>.*?)(?=^ {3}:term:|$(?!\n))/gsm

export class BitBakeDocScanner {
private _variablesInfos: Record<string, VariableInfos> = {}
private _variablesRegex = /(?!)/g // Initialize with dummy regex that won't match anything so we don't have to check for undefined

get variablesInfos (): Record<string, VariableInfos> {
return this._variablesInfos
}

get variablesRegex (): RegExp {
return this._variablesRegex
}

parse (pathToBitbakeFolder: string): void {
const file = fs.readFileSync(path.join(pathToBitbakeFolder, variablesFolder), 'utf8')
for (const match of file.matchAll(variablesRegexForDoc)) {
const name = match.groups?.name
// Naive silly inneficient incomplete conversion to markdown
const definition = match.groups?.definition
.replace(/^ {3}/gm, '')
.replace(/:term:|:ref:/g, '')
.replace(/\.\. (note|important)::/g, (_match, p1) => { return `**${p1}**` })
.replace(/::/g, ':')
.replace(/``/g, '`')
if (name === undefined || definition === undefined) {
return
}
this._variablesInfos[name] = {
name,
definition,
...variableInfosOverrides[name]
}
}
const variablesNames = Object.keys(this._variablesInfos)
// Sort from longuest to shortest in order to make the regex greedy
// Otherwise it would match B before BB_PRESERVE_ENV
variablesNames.sort((a, b) => b.length - a.length)
const variablesRegExpString = `(${variablesNames.join('|')})`
this._variablesRegex = new RegExp(variablesRegExpString, 'gi')
}
}
48 changes: 46 additions & 2 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import {
type CompletionItem,
type Definition,
ProposedFeatures,
TextDocumentSyncKind
TextDocumentSyncKind,
type Hover
} from 'vscode-languageserver/node'
import { BitBakeDocScanner } from './BitBakeDocScanner'
import { BitBakeProjectScanner } from './BitBakeProjectScanner'
import { ContextHandler } from './ContextHandler'
import { SymbolScanner } from './SymbolScanner'
Expand All @@ -27,6 +29,7 @@ const documents = new TextDocuments<TextDocument>(TextDocument)
// Until we manage to fix this, we use this documentMap to store the content of the files
// Does it have any other purpose?
const documentMap = new Map< string, string[] >()
const bitBakeDocScanner = new BitBakeDocScanner()
const bitBakeProjectScanner: BitBakeProjectScanner = new BitBakeProjectScanner(connection)
const contextHandler: ContextHandler = new ContextHandler(bitBakeProjectScanner)

Expand All @@ -53,7 +56,8 @@ connection.onInitialize((params): InitializeResult => {
commands: [
'bitbake.rescan-project'
]
}
},
hoverProvider: true
}
}
})
Expand All @@ -78,6 +82,7 @@ interface BitbakeSettings {
pathToBashScriptInterpreter: string
machine: string
generateWorkingFolder: boolean
pathToBitbakeFolder: string
}

function setSymbolScanner (newSymbolScanner: SymbolScanner | null): void {
Expand All @@ -93,6 +98,8 @@ connection.onDidChangeConfiguration((change) => {
bitBakeProjectScanner.generateWorkingPath = settings.bitbake.generateWorkingFolder
bitBakeProjectScanner.scriptInterpreter = settings.bitbake.pathToBashScriptInterpreter
bitBakeProjectScanner.machineName = settings.bitbake.machine
const bitBakeFolder = settings.bitbake.pathToBitbakeFolder
bitBakeDocScanner.parse(bitBakeFolder)
})

connection.onDidChangeWatchedFiles((change) => {
Expand Down Expand Up @@ -163,5 +170,42 @@ connection.onDefinition((textDocumentPositionParams: TextDocumentPositionParams)
return contextHandler.getDefinition(textDocumentPositionParams, documentAsText)
})

connection.onHover(async (params): Promise<Hover | undefined> => {
const { position, textDocument } = params
const documentAsText = documentMap.get(textDocument.uri)
const textLine = documentAsText?.[position.line]
if (textLine === undefined) {
return undefined
}
const matches = textLine.matchAll(bitBakeDocScanner.variablesRegex)
for (const match of matches) {
const name = match[1].toUpperCase()
if (name === undefined || match.index === undefined) {
continue
}
const start = match.index
const end = start + name.length
if ((start > position.character) || (end <= position.character)) {
continue
}

const definition = bitBakeDocScanner.variablesInfos[name]?.definition
const hover: Hover = {
contents: {
kind: 'markdown',
value: `**${name}**\n___\n${definition}`
},
range: {
start: position,
end: {
...position,
character: end
}
}
}
return hover
}
})

// Listen on the connection
connection.listen()

0 comments on commit 5b8941f

Please sign in to comment.