-
Notifications
You must be signed in to change notification settings - Fork 214
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(nxls): path completion for files and directories (#1326)
- Loading branch information
Showing
18 changed files
with
339 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { | ||
ASTNode, | ||
CompletionItem, | ||
JSONSchema, | ||
TextDocument, | ||
} from 'vscode-json-languageservice'; | ||
import { | ||
CompletionType, | ||
hasCompletionGlob, | ||
hasCompletionType, | ||
X_COMPLETION_GLOB, | ||
X_COMPLETION_TYPE, | ||
} from '@nx-console/json-schema'; | ||
import { pathCompletion } from './path-completion'; | ||
import { targetCompletion } from './target-completion'; | ||
|
||
export async function getCompletionItems( | ||
workingPath: string | undefined, | ||
schema: JSONSchema, | ||
node: ASTNode, | ||
document: TextDocument | ||
): Promise<CompletionItem[]> { | ||
if (!workingPath) { | ||
return []; | ||
} | ||
|
||
const items = completionItems(workingPath, node, document); | ||
|
||
if (hasCompletionType(schema)) { | ||
const completion = schema[X_COMPLETION_TYPE]; | ||
if (hasCompletionGlob(schema)) { | ||
return items(completion, schema[X_COMPLETION_GLOB]); | ||
} | ||
|
||
return items(completion); | ||
} else { | ||
return []; | ||
} | ||
} | ||
|
||
function completionItems( | ||
workingPath: string, | ||
node: ASTNode, | ||
document: TextDocument | ||
) { | ||
return async ( | ||
completion: CompletionType, | ||
glob?: string | ||
): Promise<CompletionItem[]> => { | ||
switch (completion) { | ||
case 'file': { | ||
return pathCompletion(workingPath, node, document, { | ||
glob: glob ?? '**/*.*', | ||
searchType: 'file', | ||
}); | ||
} | ||
case 'directory': { | ||
return pathCompletion(workingPath, node, document, { | ||
glob: glob ?? '*', | ||
searchType: 'directory', | ||
}); | ||
} | ||
case 'target': { | ||
return targetCompletion(workingPath, node, document); | ||
} | ||
default: { | ||
return []; | ||
} | ||
} | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import fastGlob from 'fast-glob'; | ||
import { | ||
ASTNode, | ||
CompletionItem, | ||
CompletionItemKind, | ||
TextDocument, | ||
} from 'vscode-json-languageservice'; | ||
import { | ||
isObjectNode, | ||
isPropertyNode, | ||
isStringNode, | ||
} from '../utils/node-types'; | ||
|
||
export async function pathCompletion( | ||
workingPath: string | undefined, | ||
node: ASTNode, | ||
document: TextDocument, | ||
options?: { | ||
glob: string; | ||
searchType: 'file' | 'directory'; | ||
supportsInterpolation?: boolean; | ||
} | ||
): Promise<CompletionItem[]> { | ||
const items: CompletionItem[] = []; | ||
|
||
if (!workingPath) { | ||
return items; | ||
} | ||
|
||
const { supportsInterpolation, glob, searchType } = { | ||
supportsInterpolation: false, | ||
...options, | ||
}; | ||
|
||
if (!isStringNode(node)) { | ||
return items; | ||
} | ||
|
||
const projectRoot = findProjectRoot(node); | ||
|
||
const files = await fastGlob([workingPath + '/**/' + glob], { | ||
ignore: ['**/node_modules/**'], | ||
dot: true, | ||
onlyFiles: searchType === 'file', | ||
onlyDirectories: searchType === 'directory', | ||
objectMode: true, | ||
}); | ||
|
||
for (const file of files) { | ||
if ( | ||
supportsInterpolation && | ||
file.path.startsWith(workingPath + '/' + projectRoot) | ||
) { | ||
const label = | ||
'{projectRoot}' + | ||
file.path.replace(workingPath + '/' + projectRoot, ''); | ||
|
||
items.push(addCompletionPathItem(label, file.path, node, document)); | ||
} | ||
|
||
if (file.path.startsWith(workingPath)) { | ||
const label = file.path.replace(workingPath + '/', ''); | ||
items.push(addCompletionPathItem(label, file.path, node, document)); | ||
|
||
if (supportsInterpolation) { | ||
const label = '{workspaceRoot}' + file.path.replace(workingPath, ''); | ||
items.push(addCompletionPathItem(label, file.path, node, document)); | ||
} | ||
} | ||
} | ||
|
||
return items; | ||
} | ||
|
||
/** | ||
* Get the first `root` property from the current node to determine `${projectRoot}` | ||
* @param node | ||
* @returns | ||
*/ | ||
function findProjectRoot(node: ASTNode): string { | ||
if (isObjectNode(node)) { | ||
for (const child of node.children) { | ||
if (isPropertyNode(child)) { | ||
if ( | ||
(child.keyNode.value === 'root' || | ||
child.keyNode.value === 'sourceRoot') && | ||
isStringNode(child.valueNode) | ||
) { | ||
return child.valueNode?.value; | ||
} | ||
} | ||
} | ||
} | ||
|
||
if (node.parent) { | ||
return findProjectRoot(node.parent); | ||
} | ||
|
||
return ''; | ||
} | ||
|
||
function addCompletionPathItem( | ||
label: string, | ||
path: string, | ||
node: ASTNode, | ||
document: TextDocument | ||
): CompletionItem { | ||
const startPosition = document.positionAt(node.offset); | ||
const endPosition = document.positionAt(node.offset + node.length); | ||
label = `"${label}"`; | ||
return { | ||
label, | ||
kind: CompletionItemKind.File, | ||
insertText: label, | ||
insertTextFormat: 2, | ||
textEdit: { | ||
newText: label, | ||
range: { | ||
start: startPosition, | ||
end: endPosition, | ||
}, | ||
}, | ||
detail: path, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { | ||
ASTNode, | ||
CompletionItem, | ||
TextDocument, | ||
} from 'vscode-json-languageservice'; | ||
|
||
export async function targetCompletion( | ||
workingPath: string | undefined, | ||
node: ASTNode, | ||
document: TextDocument | ||
): Promise<CompletionItem[]> { | ||
return []; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
/** | ||
* Combines the second array with the first array, without having to loop or change the reference of the first array. | ||
* @param arr1 | ||
* @param arr2 | ||
*/ | ||
export function mergeArrays(arr1: Array<unknown>, arr2: Array<unknown>) { | ||
Array.prototype.push.apply(arr1, arr2); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { | ||
ASTNode, | ||
ObjectASTNode, | ||
PropertyASTNode, | ||
StringASTNode, | ||
} from 'vscode-json-languageservice'; | ||
|
||
export function isPropertyNode(node?: ASTNode): node is PropertyASTNode { | ||
return node?.type === 'property'; | ||
} | ||
|
||
export function isObjectNode(node?: ASTNode): node is ObjectASTNode { | ||
return node?.type === 'object'; | ||
} | ||
|
||
export function isStringNode(node?: ASTNode): node is StringASTNode { | ||
return node?.type === 'string'; | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './lib/workspace-json-schema'; | ||
export * from './lib/project-json-schema'; | ||
export * from './lib/completion-type'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { JSONSchema } from 'vscode-json-languageservice'; | ||
import { hasKey } from '@nx-console/utils/shared'; | ||
|
||
export const X_COMPLETION_TYPE = 'x-completion-type' as const; | ||
export const X_COMPLETION_GLOB = 'x-completion-glob' as const; | ||
|
||
export type CompletionType = 'file' | 'directory' | 'target'; | ||
|
||
export function hasCompletionType( | ||
schema: JSONSchema | ||
): schema is JSONSchema & { [X_COMPLETION_TYPE]: CompletionType } { | ||
return hasKey(schema, X_COMPLETION_TYPE); | ||
} | ||
|
||
export function hasCompletionGlob( | ||
schema: JSONSchema | ||
): schema is JSONSchema & { [X_COMPLETION_GLOB]: string } { | ||
return hasKey(schema, X_COMPLETION_GLOB); | ||
} |
Oops, something went wrong.