Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update from upstream #3

Merged
merged 7 commits into from
Sep 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

Releases of the extension can be downloaded from [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=denoland.vscode-deno).

### [v2.1.2](https://github.com/denoland/vscode_deno/compare/v2.1.1...v2.1.2) / 2020.09.04

- fix: another typescript not found error (#178)

### [v2.1.1](https://github.com/denoland/vscode_deno/compare/v2.1.0...v2.1.1) / 2020.09.04

- fix: typescript not found error (#177)

### [v2.1.0](https://github.com/denoland/vscode_deno/compare/v2.0.16...v2.1.0) / 2020.09.04

- feat: IntelliSense support for std and deno.land/x imports (#172)
- fix: add support for URLs with non default ports (#173)
- fix: correctly handle non existing \$DENO_DIR/deps (#169)
- refactor: simplify import map json validation (#167)
- chore: update dependencies (#165)
- docs: remove non english readme (#164)

### [v2.0.16](https://github.com/denoland/vscode_deno/compare/v2.0.15...v2.0.16) / 2020.08.29

- fix: autocomplete supports adding extensions (#156)
Expand Down
35 changes: 35 additions & 0 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ import execa from "execa";
import * as semver from "semver";

import { TreeViewProvider } from "./tree_view_provider";
import {
ImportEnhancementCompletionProvider,
CACHE_STATE,
} from "./import_enhancement_provider";

import { ImportMap } from "../../core/import_map";
import { HashMeta } from "../../core/hash_meta";
import { isInDeno } from "../../core/deno";
Expand Down Expand Up @@ -113,6 +118,9 @@ export class Extension {
},
executablePath: "",
};
// CGQAQ: ImportEnhancementCompletionProvider instance
private import_enhancement_completion_provider = new ImportEnhancementCompletionProvider();

// get configuration of Deno
public getConfiguration(uri?: Uri): ConfigurationField {
const config: ConfigurationField = {};
Expand Down Expand Up @@ -465,6 +473,14 @@ Executable ${this.denoInfo.executablePath}`;
await window.showInformationMessage(`Copied to clipboard.`);
});

// CGQAQ: deno._clear_import_enhencement_cache
this.registerCommand("_clear_import_enhencement_cache", async () => {
this.import_enhancement_completion_provider
.clearCache()
.then(() => window.showInformationMessage("Clear success!"))
.catch(() => window.showErrorMessage("Clear failed!"));
});

this.registerQuickFix({
_fetch_remote_module: async (editor, text) => {
const config = this.getConfiguration(editor.document.uri);
Expand Down Expand Up @@ -595,6 +611,23 @@ Executable ${this.denoInfo.executablePath}`;
window.registerTreeDataProvider("deno", treeView)
);

// CGQAQ: activate import enhance feature
this.import_enhancement_completion_provider.activate(this.context);

// CGQAQ: Start caching full module list
this.import_enhancement_completion_provider
.cacheModList()
.then((state) => {
if (state === CACHE_STATE.CACHE_SUCCESS) {
window.showInformationMessage(
"deno.land/x module list cached successfully!"
);
}
})
.catch(() =>
window.showErrorMessage("deno.land/x module list failed to cache!")
);

this.sync(window.activeTextEditor?.document);

const extension = extensions.getExtension(this.id);
Expand All @@ -607,6 +640,8 @@ Executable ${this.denoInfo.executablePath}`;
public async deactivate(context: ExtensionContext): Promise<void> {
this.context = context;

this.import_enhancement_completion_provider.dispose();

if (this.client) {
await this.client.stop();
this.client = undefined;
Expand Down
255 changes: 255 additions & 0 deletions client/src/import_enhancement_provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import {
CompletionItemProvider,
TextDocument,
Position,
CompletionItem,
Disposable,
CompletionItemKind,
CompletionList,
DocumentSelector,
languages,
ExtensionContext,
Range,
Command,
window,
ProgressLocation,
} from "vscode";

import Semver from "semver";

import VC = require("vscode-cache");

import {
listVersionsOfMod,
modTreeOf,
parseImportStatement,
searchX,
fetchModList,
ModuleInfo,
} from "./import_utils";

export enum CACHE_STATE {
ALREADY_CACHED,
CACHE_SUCCESS,
}

export class ImportEnhancementCompletionProvider
implements CompletionItemProvider, Disposable {
vc?: VC;
async provideCompletionItems(
document: TextDocument,
position: Position
// _token: CancellationToken,
// _context: CompletionContext
): Promise<CompletionItem[] | CompletionList | undefined> {
const line_text = document.lineAt(position).text;

if (/import.+?from\W+['"].*?['"]/.test(line_text)) {
// We're at import statement line
const imp_info = parseImportStatement(line_text);
if (imp_info?.domain !== "deno.land") {
return undefined;
}
// We'll handle the completion only if the domain is `deno.land` and mod name is not empty
const at_index = line_text.indexOf("@");
if (
/.*?deno.land\/(x\/)?\w+@[\w.-]*$/.test(
line_text.substring(0, position.character)
) &&
position.character > at_index
) {
// Version completion
const vers = await listVersionsOfMod(imp_info.module);

const result = vers.versions
.sort((a, b) => {
const av = Semver.clean(a);
const bv = Semver.clean(b);
if (
av === null ||
bv === null ||
!Semver.valid(av) ||
!Semver.valid(bv)
) {
return 0;
}
return Semver.gt(av, bv) ? -1 : 1;
})
.map((it, i) => {
// let latest version on top
const ci = new CompletionItem(it, CompletionItemKind.Value);
ci.sortText = `a${String.fromCharCode(i) + 1}`;
ci.filterText = it;
ci.range = new Range(
position.line,
at_index + 1,
position.line,
position.character
);
return ci;
});
return new CompletionList(result);
}

if (
/.*?deno\.land\/x\/\w*$/.test(
line_text.substring(line_text.indexOf("'") + 1, position.character)
)
) {
// x module name completion
if (this.vc !== undefined) {
const result: { name: string; description: string }[] = await searchX(
this.vc,
imp_info.module
);
const r = result.map((it) => {
const ci = new CompletionItem(it.name, CompletionItemKind.Module);
ci.detail = it.description;
ci.sortText = String.fromCharCode(1);
ci.filterText = it.name;
return ci;
});
return r;
} else {
return [];
}
}

if (
!/.*?deno\.land\/(x\/)?\w+(@[\w.-]*)?\//.test(
line_text.substring(0, position.character)
)
) {
return [];
}

const result = await modTreeOf(
this.vc,
imp_info.module,
imp_info.version
);
const arr_path = imp_info.path.split("/");
const path = arr_path.slice(0, arr_path.length - 1).join("/") + "/";

const r = result.directory_listing
.filter((it) => it.path.startsWith(path))
.map((it) => ({
path:
path.length > 1 ? it.path.replace(path, "") : it.path.substring(1),
size: it.size,
type: it.type,
}))
.filter((it) => it.path.split("/").length < 2)
.filter(
(it) =>
// exclude tests
!(it.path.endsWith("_test.ts") || it.path.endsWith("_test.js")) &&
// include only js and ts
(it.path.endsWith(".ts") ||
it.path.endsWith(".js") ||
it.path.endsWith(".tsx") ||
it.path.endsWith(".jsx") ||
it.path.endsWith(".mjs") ||
it.type !== "file") &&
// exclude privates
!it.path.startsWith("_") &&
// exclude hidden file/folder
!it.path.startsWith(".") &&
// exclude testdata dir
(it.path !== "testdata" || it.type !== "dir") &&
it.path.length !== 0
)
// .sort((a, b) => a.path.length - b.path.length)
.map((it) => {
const r = new CompletionItem(
it.path,
it.type === "dir"
? CompletionItemKind.Folder
: CompletionItemKind.File
);
r.sortText = it.type === "dir" ? "a" : "b";
r.insertText = it.type === "dir" ? it.path + "/" : it.path;
r.range = new Range(
position.line,
line_text.substring(0, position.character).lastIndexOf("/") + 1,
position.line,
position.character
);
if (it.type === "dir") {
// https://github.com/microsoft/vscode-extension-samples/blob/bb4a0c3a5dd9460a5cd64290b4d5c4f6bd79bdc4/completions-sample/src/extension.ts#L37
r.command = <Command>{
command: "editor.action.triggerSuggest",
title: "Re-trigger completions...",
};
}
return r;
});
return new CompletionList(r, false);
}
}

async clearCache(): Promise<void> {
await this.vc?.flush();
}

async cacheModList(): Promise<CACHE_STATE> {
return window.withProgress(
{
location: ProgressLocation.Notification,
title: "Fetching deno.land/x module list...",
},
async (progress) => {
const mod_list_key = "mod_list";
if (this.vc?.isExpired(mod_list_key) || !this.vc?.has(mod_list_key)) {
this.vc?.forget(mod_list_key);
progress.report({ increment: 0 });
for await (const modules of fetchModList()) {
if (this.vc?.has(mod_list_key)) {
const list_in_cache = this.vc?.get(mod_list_key) as ModuleInfo[];
list_in_cache.push(...modules.data);
await this.vc?.put(
mod_list_key,
list_in_cache,
60 * 60 * 24 * 7 /* expiration in a week */
);
} else {
this.vc?.put(
mod_list_key,
modules.data,
60 * 60 * 24 * 7 /* expiration in a week */
);
}
progress.report({
increment: (1 / modules.total) * 100,
});
}
return CACHE_STATE.CACHE_SUCCESS;
}
return CACHE_STATE.ALREADY_CACHED;
}
);
}

activate(ctx: ExtensionContext): void {
this.vc = new VC(ctx, "import-enhanced");

const document_selector = <DocumentSelector>[
{ language: "javascript" },
{ language: "typescript" },
{ language: "javascriptreact" },
{ language: "typescriptreact" },
];
const trigger_word = ["@", "/"];
ctx.subscriptions.push(
languages.registerCompletionItemProvider(
document_selector,
this,
...trigger_word
)
);
}

dispose(): void {
/* eslint-disable */
}
}
Loading