Skip to content

Commit

Permalink
feat: Implement autocomplete for import paths.
Browse files Browse the repository at this point in the history
  • Loading branch information
Jackson Dean committed Nov 5, 2019
1 parent 3912c56 commit bc0316d
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 7 deletions.
1 change: 1 addition & 0 deletions packages/@css-blocks/language-server/package.json
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"@css-blocks/core": "^0.24.0",
"@glimmer/syntax": "^0.42.1",
"glob": "^7.1.5",
"opticss": "^0.6.2",
"vscode-languageserver": "^5.2.1",
"vscode-uri": "^2.0.3"
Expand Down
Expand Up @@ -2,15 +2,22 @@ import { BlockFactory } from "@css-blocks/core/dist/src";
import { CompletionItem, TextDocumentPositionParams, TextDocuments } from "vscode-languageserver";

import { PathTransformer } from "../pathTransformers/PathTransformer";
import { getBlockCompletions, isBlockFile } from "../util/blockUtils";
import { getHbsCompletions } from "../util/hbsCompletionProvider";
import { isTemplateFile } from "../util/hbsUtils";

export async function emberCompletionProvider(documents: TextDocuments, factory: BlockFactory, params: TextDocumentPositionParams, pathTransformer: PathTransformer): Promise<CompletionItem[]> {
const document = documents.get(params.textDocument.uri);
if (document) {
if (isTemplateFile(document.uri)) {
return await getHbsCompletions(document, params.position, factory, pathTransformer);
}

if (!document) {
return [];
}

if (isTemplateFile(document.uri)) {
return await getHbsCompletions(document, params.position, factory, pathTransformer);
} else if (isBlockFile(document.uri)) {
return await getBlockCompletions(document, params.position);
} else {
return [];
}
return [];
}
Expand Up @@ -5,7 +5,7 @@ import { URI } from "vscode-uri";

import { isBlockFile } from "../util/blockUtils";

const LINK_REGEX = /from\s+(['"])([^'"]+)\1;/;
export const LINK_REGEX = /from\s+(['"])([^'"]+)\1;?/;

export async function blockLinksProvider(documents: TextDocuments, params: DocumentLinkParams): Promise<DocumentLink[]> {
let { uri } = params.textDocument;
Expand Down
Expand Up @@ -11,6 +11,6 @@ export const SERVER_CAPABILITIES: ServerCapabilities = {
documentSymbolProvider: false,
completionProvider: {
resolveProvider: false,
"triggerCharacters": [ ":", '"', "=" ],
triggerCharacters: [ ":", '"', "=", ".", "/" ],
},
};
82 changes: 82 additions & 0 deletions packages/@css-blocks/language-server/src/util/blockUtils.ts
@@ -1,7 +1,13 @@
import { CssBlockError, Syntax } from "@css-blocks/core/dist/src";
import { BlockParser } from "@css-blocks/core/dist/src/BlockParser/BlockParser";
import * as fs from "fs";
import * as glob from "glob";
import { postcss } from "opticss";
import * as path from "path";
import { CompletionItem, CompletionItemKind, Position, TextDocument } from "vscode-languageserver";
import { URI } from "vscode-uri";

import { LINK_REGEX } from "../documentLinksProviders/blockLinkProvider";

// TODO: Currently we are only supporting css. This should eventually support all
// of the file types supported by css blocks
Expand Down Expand Up @@ -29,3 +35,79 @@ export async function parseBlockErrors(parser: BlockParser, blockFsPath: string,

return errors;
}

/**
* If the cursor line has an import path, we check to see if the current position
* of the cursor in the line is within the bounds of the import path to decide
* whether to provide import path completions.
*/
function shouldCompleteImportPath(importPathMatches: RegExpMatchArray, position: Position, lineText: string): boolean {
let relativeImportPath = importPathMatches[2];
let relativeImportPathStartLinePosition = lineText.indexOf(relativeImportPath);
let relativeImportPathEndLinePosition = relativeImportPathStartLinePosition + relativeImportPath.length;
return relativeImportPathStartLinePosition <= position.character && relativeImportPathEndLinePosition >= position.character;
}

async function getImportPathCompletions(documentUri: string, relativeImportPath: string): Promise<CompletionItem[]> {
let completionItems: CompletionItem[] = [];

// if the user has only typed leading dots, don't complete anything.
if (/^\.+$/.test(relativeImportPath)) {
return completionItems;
}

let blockDirPath = path.dirname(URI.parse(documentUri).fsPath);
let absoluteImportPath = path.resolve(blockDirPath, relativeImportPath);
let globPatternSuffix = relativeImportPath.endsWith("/") ? "/*" : "*";
let blockSyntax = path.extname(documentUri);

return new Promise(outerResolve => {
glob(`${absoluteImportPath}${globPatternSuffix}`, async (_, pathNames) => {
let items: (CompletionItem | null)[] = await Promise.all(pathNames.map(pathName => {
return new Promise(innerResolve => {
fs.stat(pathName, (_, stats) => {
let completionKind: CompletionItemKind | undefined;

if (stats.isDirectory()) {
completionKind = CompletionItemKind.Folder;
} else if (stats.isFile() && path.extname(pathName) === blockSyntax) {
completionKind = CompletionItemKind.File;
}

if (!completionKind) {
innerResolve(null);
}

innerResolve({
label: path.basename(pathName),
kind: completionKind,
});
});
});
}));

// NOTE: it seems typescript is not happy with items.filter(Boolean)
items.forEach(item => {
if (item) {
completionItems.push(item);
}
});

outerResolve(completionItems);
});
});
}

// TODO: handle other completion cases (extending imported block, etc);
export async function getBlockCompletions(document: TextDocument, position: Position): Promise<CompletionItem[]> {
let text = document.getText();
let lineAtCursor = text.split(/\r?\n/)[position.line];
let importPathMatches = lineAtCursor.match(LINK_REGEX);

if (importPathMatches && shouldCompleteImportPath(importPathMatches, position, lineAtCursor)) {
let relativeImportPath = importPathMatches[2];
return await getImportPathCompletions(document.uri, relativeImportPath);
}

return [];
}
11 changes: 11 additions & 0 deletions yarn.lock
Expand Up @@ -8532,6 +8532,17 @@ glob@^7.1.4:
once "^1.3.0"
path-is-absolute "^1.0.0"

glob@^7.1.5:
version "7.1.5"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz#6714c69bee20f3c3e64c4dd905553e532b40cdc0"
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"

global-dirs@^0.1.0, global-dirs@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
Expand Down

0 comments on commit bc0316d

Please sign in to comment.