Skip to content
Permalink
Browse files

refactor: Remove the use of glob for file path completions.

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 e26563a3500e22dc3aad00fb533e54f274e2b3ba
@@ -15,7 +15,6 @@
"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"
@@ -5,7 +5,7 @@ import { URI } from "vscode-uri";

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[]> {
let { uri } = params.textDocument;
@@ -1,2 +1,4 @@
@block randoBlock from "../";
@export utils from "../blocks/";
@block aBlock from "../blocks/ut";
@export bBlock from "./";
@@ -360,6 +360,7 @@ export class LanguageServerServerTest {

this.startServer();

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

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

// file name completions
const params2: TextDocumentPositionParams = {
textDocument: {
uri: importCompletionsFixtureUri,
@@ -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(
response, [
response2, [
{
"kind": 17,
"label": "block-with-errors.block.css",
@@ -406,6 +409,55 @@ export class LanguageServerServerTest {
"kind": 17,
"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");
}
}
@@ -1,13 +1,12 @@
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";
const IMPORT_PATH_REGEX = /from\s+(['"])([^'"]+)/;

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

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

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

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

return null;
return null;
}

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

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

let relativeScanDir = relativeImportPath.endsWith(path.sep) ? relativeImportPath : relativeImportPath.substring(0, relativeImportPath.lastIndexOf(path.sep) + 1);
let absoluteScanDir = path.resolve(blockDirPath, relativeScanDir);
let blockFsPath = URI.parse(documentUri).fsPath;
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 => {
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) => {
let completionItem = maybeCompletionItem(fileInfo, currentFileExtension);
return fileInfos.reduce(
(completionItems: CompletionItem[], fileInfo) => {
if (fileInfo.pathName === blockFsPath) {
return completionItems;
}

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

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

return completionItems;
},
[]);
}

// TODO: handle other completion cases (extending imported block, etc). Right
// this is only providing completions for import path;
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);
let importPathMatches = lineAtCursor.match(IMPORT_PATH_REGEX);

if (importPathMatches && shouldCompleteImportPath(importPathMatches, position, lineAtCursor)) {
let relativeImportPath = importPathMatches[2];
@@ -8532,17 +8532,6 @@ 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"

0 comments on commit e26563a

Please sign in to comment.
You can’t perform that action at this time.