Skip to content

Commit

Permalink
refactor: Remove the use of glob for file path completions.
Browse files Browse the repository at this point in the history
This was using glob naively to filter the completion items sent back to
the client editor. However, this is not really necessary since the client
filters the list the UI as the user types.
  • Loading branch information
Jackson Dean committed Nov 6, 2019
1 parent 15d91b2 commit e26563a
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 52 deletions.
1 change: 0 additions & 1 deletion packages/@css-blocks/language-server/package.json
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"dependencies": { "dependencies": {
"@css-blocks/core": "^0.24.0", "@css-blocks/core": "^0.24.0",
"@glimmer/syntax": "^0.42.1", "@glimmer/syntax": "^0.42.1",
"glob": "^7.1.5",
"opticss": "^0.6.2", "opticss": "^0.6.2",
"vscode-languageserver": "^5.2.1", "vscode-languageserver": "^5.2.1",
"vscode-uri": "^2.0.3" "vscode-uri": "^2.0.3"
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { URI } from "vscode-uri";


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


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


export async function blockLinksProvider(documents: TextDocuments, params: DocumentLinkParams): Promise<DocumentLink[]> { export async function blockLinksProvider(documents: TextDocuments, params: DocumentLinkParams): Promise<DocumentLink[]> {
let { uri } = params.textDocument; let { uri } = params.textDocument;
Expand Down
Original file line number Original file line Diff line number Diff line change
@@ -1,2 +1,4 @@
@block randoBlock from "../"; @block randoBlock from "../";
@export utils from "../blocks/"; @export utils from "../blocks/";
@block aBlock from "../blocks/ut";
@export bBlock from "./";
70 changes: 61 additions & 9 deletions packages/@css-blocks/language-server/src/test/server-test.ts
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ export class LanguageServerServerTest {


this.startServer(); this.startServer();


// directory completions
const params1: TextDocumentPositionParams = { const params1: TextDocumentPositionParams = {
textDocument: { textDocument: {
uri: importCompletionsFixtureUri, uri: importCompletionsFixtureUri,
Expand All @@ -373,17 +374,19 @@ export class LanguageServerServerTest {
const response1 = await this.mockClientConnection.sendRequest(CompletionRequest.type, params1); const response1 = await this.mockClientConnection.sendRequest(CompletionRequest.type, params1);


assert.deepEqual( assert.deepEqual(
response1, [ response1, [
{ {
"kind": 19, kind: 19,
"label": "blocks", label: "blocks",
}, },
{ {
"kind": 19, kind: 19,
"label": "components", label: "components",
}, },
], "it returns the expected folder completions"); ],
"it returns the expected folder completions");


// file name completions
const params2: TextDocumentPositionParams = { const params2: TextDocumentPositionParams = {
textDocument: { textDocument: {
uri: importCompletionsFixtureUri, uri: importCompletionsFixtureUri,
Expand All @@ -394,10 +397,10 @@ export class LanguageServerServerTest {
}, },
}; };


const response = await this.mockClientConnection.sendRequest(CompletionRequest.type, params2); const response2 = await this.mockClientConnection.sendRequest(CompletionRequest.type, params2);


assert.deepEqual( assert.deepEqual(
response, [ response2, [
{ {
"kind": 17, "kind": 17,
"label": "block-with-errors.block.css", "label": "block-with-errors.block.css",
Expand All @@ -406,6 +409,55 @@ export class LanguageServerServerTest {
"kind": 17, "kind": 17,
"label": "utils.block.css", "label": "utils.block.css",
}, },
], "it returns the expected file completions"); ],
"it returns the expected file completions");

// partially typed filename completion
const params3: TextDocumentPositionParams = {
textDocument: {
uri: importCompletionsFixtureUri,
},
position: {
line: 2,
character: 32,
},
};

const response3 = await this.mockClientConnection.sendRequest(CompletionRequest.type, params3);

assert.deepEqual(
response3, [
{
kind: 17,
label: "block-with-errors.block.css",
},
{
kind: 17,
label: "utils.block.css",
},
],
"it returns the expected file completions");

// local directory reference
const params4: TextDocumentPositionParams = {
textDocument: {
uri: importCompletionsFixtureUri,
},
position: {
line: 3,
character: 23,
},
};

const response4 = await this.mockClientConnection.sendRequest(CompletionRequest.type, params4);

assert.deepEqual(
response4, [
{
kind: 17,
label: "a.block.css",
},
],
"it returns the expected file completions");
} }
} }
65 changes: 35 additions & 30 deletions packages/@css-blocks/language-server/src/util/blockUtils.ts
Original file line number Original file line Diff line number Diff line change
@@ -1,13 +1,12 @@
import { CssBlockError, Syntax } from "@css-blocks/core/dist/src"; import { CssBlockError, Syntax } from "@css-blocks/core/dist/src";
import { BlockParser } from "@css-blocks/core/dist/src/BlockParser/BlockParser"; import { BlockParser } from "@css-blocks/core/dist/src/BlockParser/BlockParser";
import * as fs from "fs"; import * as fs from "fs";
import * as glob from "glob";
import { postcss } from "opticss"; import { postcss } from "opticss";
import * as path from "path"; import * as path from "path";
import { CompletionItem, CompletionItemKind, Position, TextDocument } from "vscode-languageserver"; import { CompletionItem, CompletionItemKind, Position, TextDocument } from "vscode-languageserver";
import { URI } from "vscode-uri"; import { URI } from "vscode-uri";


import { LINK_REGEX } from "../documentLinksProviders/blockLinkProvider"; const IMPORT_PATH_REGEX = /from\s+(['"])([^'"]+)/;


// TODO: Currently we are only supporting css. This should eventually support all // TODO: Currently we are only supporting css. This should eventually support all
// of the file types supported by css blocks // of the file types supported by css blocks
Expand Down Expand Up @@ -53,23 +52,23 @@ interface PathCompletionCandidateInfo {
stats: fs.Stats; stats: fs.Stats;
} }


function maybeCompletionItem(completionCandidateInfo: PathCompletionCandidateInfo, currentFileExtension: string): CompletionItem | null { function maybeCompletionItem(completionCandidateInfo: PathCompletionCandidateInfo): CompletionItem | null {
let completionKind: CompletionItemKind | undefined; let completionKind: CompletionItemKind | undefined;


if (completionCandidateInfo.stats.isDirectory()) { if (completionCandidateInfo.stats.isDirectory()) {
completionKind = CompletionItemKind.Folder; completionKind = CompletionItemKind.Folder;
} else if (completionCandidateInfo.stats.isFile() && completionCandidateInfo.pathName.endsWith(`.block${currentFileExtension}`)) { } else if (completionCandidateInfo.stats.isFile() && completionCandidateInfo.pathName.indexOf(".block.") >= 0) {
completionKind = CompletionItemKind.File; completionKind = CompletionItemKind.File;
} }


if (completionKind) { if (completionKind) {
return { return {
label: path.basename(completionCandidateInfo.pathName), label: path.basename(completionCandidateInfo.pathName),
kind: completionKind, kind: completionKind,
}; };
} }


return null; return null;
} }


async function getImportPathCompletions(documentUri: string, relativeImportPath: string): Promise<CompletionItem[]> { async function getImportPathCompletions(documentUri: string, relativeImportPath: string): Promise<CompletionItem[]> {
Expand All @@ -81,37 +80,43 @@ async function getImportPathCompletions(documentUri: string, relativeImportPath:
} }


let blockDirPath = path.dirname(URI.parse(documentUri).fsPath); let blockDirPath = path.dirname(URI.parse(documentUri).fsPath);
let absoluteImportPath = path.resolve(blockDirPath, relativeImportPath); let relativeScanDir = relativeImportPath.endsWith(path.sep) ? relativeImportPath : relativeImportPath.substring(0, relativeImportPath.lastIndexOf(path.sep) + 1);
let globPatternSuffix = relativeImportPath.endsWith("/") ? "/*" : "*"; let absoluteScanDir = path.resolve(blockDirPath, relativeScanDir);
let currentFileExtension = path.extname(documentUri); let blockFsPath = URI.parse(documentUri).fsPath;

let pathNames: string[] = await new Promise(r => { let pathNames: string[] = await new Promise(r => {
glob(`${absoluteImportPath}${globPatternSuffix}`, async (_, paths) => r(paths)); fs.readdir(absoluteScanDir, (_, paths) => r(paths || []));
}); });


let fileInfos: PathCompletionCandidateInfo[] = await Promise.all(pathNames.map(pathName => { let fileInfos: PathCompletionCandidateInfo[] = await Promise.all(pathNames.map(pathName => {
return new Promise(r => { return new Promise(r => {
fs.stat(pathName, (_, stats) => r({ pathName, stats })); let absolutePath = `${absoluteScanDir}/${pathName}`;
fs.stat(absolutePath, (_, stats) => r({ pathName: absolutePath, stats }));
}); });
})); }));


return fileInfos.reduce((completionItems: CompletionItem[], fileInfo) => { return fileInfos.reduce(
let completionItem = maybeCompletionItem(fileInfo, currentFileExtension); (completionItems: CompletionItem[], fileInfo) => {
if (fileInfo.pathName === blockFsPath) {
return completionItems;
}


if (completionItem) { let completionItem = maybeCompletionItem(fileInfo);
completionItems.push(completionItem);
}


return completionItems; if (completionItem) {
}, []); completionItems.push(completionItem);
}

return completionItems;
},
[]);
} }


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


if (importPathMatches && shouldCompleteImportPath(importPathMatches, position, lineAtCursor)) { if (importPathMatches && shouldCompleteImportPath(importPathMatches, position, lineAtCursor)) {
let relativeImportPath = importPathMatches[2]; let relativeImportPath = importPathMatches[2];
Expand Down
11 changes: 0 additions & 11 deletions yarn.lock
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -8532,17 +8532,6 @@ glob@^7.1.4:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.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: global-dirs@^0.1.0, global-dirs@^0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
Expand Down

0 comments on commit e26563a

Please sign in to comment.