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

Feat/upstream #71

Merged
merged 15 commits into from
Nov 11, 2023
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
5 changes: 4 additions & 1 deletion .vim/coc-settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"cSpell.words": [
"mutex"
"Ngcc",
"mutex",
"nvim",
"untrusted"
]
}
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Angular Language Service

> fork from [angular/vscode-ng-language-service](https://github.com/angular/vscode-ng-language-service) v13.3.4
> [commit](https://github.com/angular/vscode-ng-language-service/commit/6d1a664e05ec569d96afdcfc871acb176e8ff846)
> fork from [angular/vscode-ng-language-service](https://github.com/angular/vscode-ng-language-service) v17.0.1
> [commit](https://github.com/angular/vscode-ng-language-service/commit/1dd740af951782d8ae19420bc553dee305b02eac)

An angular language service coc extension for (neo)vim 💖

**Note:** require nodejs >= v12 for `view-engine` and nodejs >= v14 for `lvy`
**Note:** only version <= 13.3.6 support view-engine

## Install

Expand Down Expand Up @@ -39,7 +39,6 @@ and external templates including:

- `angular.trace.server` enable angular language server trace log
- `angular.log` Enables logging of the Angular server to a file. This log can be used to diagnose Angular Server issues. The log may contain file paths, source code, and other potentially sensitive information from your project.
- `angular.view-engine` Use legacy View Engine language service.
- `angular.suggest.includeAutomaticOptionalChainCompletions` Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires TS 3.7+ and strict null checks to be enabled.
- `angular.suggest.includeCompletionsWithSnippetText` Enable/disable snippet completions from Angular language server. Requires using TypeScript 4.3+ in the workspace and the `legacy View Engine` option to be disabled.

Expand Down
38 changes: 20 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "coc-angular",
"description": "Editor services for Angular templates",
"version": "13.3.6",
"version": "17.0.1",
"keywords": [
"coc.nvim",
"angular",
Expand All @@ -16,12 +16,15 @@
"url": "https://github.com/iamcco/coc-angular"
},
"engines": {
"coc": "^0.0.80"
"coc": "^0.0.82"
},
"capabilities": {
"untrustedWorkspaces": {
"supported": false,
"description": "This extension requires workspace trust because it needs to execute ngcc from the node_modules in the workspace."
"supported": true
},
"virtualWorkspaces": {
"supported": "limited",
"description": "The Language Server Protocol does not support remote file systems. Functionality is limited to syntax highlighting only."
}
},
"main": "./out/index.js",
Expand Down Expand Up @@ -81,11 +84,6 @@
"default": "off",
"description": "Enables logging of the Angular server to a file. This log can be used to diagnose Angular Server issues. The log may contain file paths, source code, and other potentially sensitive information from your project."
},
"angular.view-engine": {
"type": "boolean",
"default": false,
"description": "Use legacy View Engine language service. This option is incompatible with projects using Angular v13 and above."
},
"angular.enable-strict-mode-prompt": {
"type": "boolean",
"default": true,
Expand All @@ -100,6 +98,11 @@
"type": "boolean",
"default": true,
"description": "Enable/disable snippet completions from Angular language server. Requires using TypeScript 4.3+ in the workspace and the `legacy View Engine` option to be disabled."
},
"angular.forceStrictTemplates": {
"type": "boolean",
"default": false,
"markdownDescription": "Enabling this option will force the language service to use [strictTemplates](https://angular.io/guide/angular-compiler-options#stricttemplates) and ignore the user settings in the `tsconfig.json`."
}
}
}
Expand All @@ -109,16 +112,15 @@
"watch": "tsc -w -p ./"
},
"devDependencies": {
"@types/node": "^10.9.4",
"coc.nvim": "^0.0.80",
"ts-loader": "^8.0.14",
"vscode-languageserver-protocol": "^3.16.0",
"webpack": "^5.19.0",
"webpack-cli": "^4.4.0"
"@types/node": "^20.9.0",
"coc.nvim": "^0.0.83-next.9",
"ts-loader": "^9.5.0",
"vscode-languageserver-protocol": "^3.17.5",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"v12_language_service": "file:v12_language_service",
"@angular/language-server": "13.3.4",
"typescript": "~4.6.2"
"@angular/language-server": "17.0.1",
"typescript": "5.2.2"
}
}
124 changes: 73 additions & 51 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import * as vscode from 'coc.nvim';

import {OpenOutputChannel, ProjectLoadingFinish, ProjectLoadingStart, SuggestStrictMode, SuggestStrictModeParams} from './common/notifications';
import {GetCompleteItems, GetComponentsWithTemplateFile, GetHoverInfo, GetTcbRequest, GetTemplateLocationForComponent, IsInAngularProject} from './common/requests';
import {provideCompletionItem} from './middleware/provideCompletionItem';
import {resolve, Version} from './common/resolver';
import {NodeModule, resolve} from './common/resolver';

import {isInsideComponentDecorator, isInsideInlineTemplateRegion, isInsideStringLiteral} from './embedded_support';
import {code2ProtocolConverter, protocol2CodeConverter} from './common/utils';
Expand Down Expand Up @@ -63,10 +62,22 @@ export class AngularLanguageClient implements vscode.Disposable {
// Don't let our output console pop open
revealOutputChannelOn: vscode.RevealOutputChannelOn.Never,
outputChannel: this.outputChannel,
markdown: {
isTrusted: true,
},
// middleware
middleware: {
provideCodeActions: async (
document: vscode.LinesTextDocument, range: vscode.Range, context: vscode.CodeActionContext,
token: vscode.CancellationToken, next: vscode.ProvideCodeActionsSignature) => {
if (await this.isInAngularProject(document) &&
isInsideInlineTemplateRegion(document, range.start) &&
isInsideInlineTemplateRegion(document, range.end)) {
return next(document, range, context, token);
}
},
prepareRename: async (
document: vscode.TextDocument, position: vscode.Position,
document: vscode.LinesTextDocument, position: vscode.Position,
token: vscode.CancellationToken, next: vscode.PrepareRenameSignature) => {
// We are able to provide renames for many types of string literals: template strings,
// pipe names, and hopefully in the future selectors and input/output aliases. Because
Expand All @@ -82,23 +93,23 @@ export class AngularLanguageClient implements vscode.Disposable {
}
},
provideDefinition: async (
document: vscode.TextDocument, position: vscode.Position,
document: vscode.LinesTextDocument, position: vscode.Position,
token: vscode.CancellationToken, next: vscode.ProvideDefinitionSignature) => {
if (await this.isInAngularProject(document) &&
isInsideComponentDecorator(document, position)) {
return next(document, position, token);
}
},
provideTypeDefinition: async (
document: vscode.TextDocument, position: vscode.Position,
document: vscode.LinesTextDocument, position: vscode.Position,
token: vscode.CancellationToken, next) => {
if (await this.isInAngularProject(document) &&
isInsideInlineTemplateRegion(document, position)) {
return next(document, position, token);
}
},
provideHover: async (
document: vscode.TextDocument, position: vscode.Position,
document: vscode.LinesTextDocument, position: vscode.Position,
token: vscode.CancellationToken, next: vscode.ProvideHoverSignature) => {
if (!(await this.isInAngularProject(document)) ||
!isInsideInlineTemplateRegion(document, position)) {
Expand Down Expand Up @@ -126,7 +137,7 @@ export class AngularLanguageClient implements vscode.Disposable {
return angularResultsPromise;
},
provideSignatureHelp: async (
document: vscode.TextDocument, position: vscode.Position,
document: vscode.LinesTextDocument, position: vscode.Position,
context: vscode.SignatureHelpContext, token: vscode.CancellationToken,
next: vscode.ProvideSignatureHelpSignature) => {
if (await this.isInAngularProject(document) &&
Expand All @@ -135,7 +146,7 @@ export class AngularLanguageClient implements vscode.Disposable {
}
},
provideCompletionItem: async (
document: vscode.TextDocument, position: vscode.Position,
document: vscode.LinesTextDocument, position: vscode.Position,
context: vscode.CompletionContext, token: vscode.CancellationToken,
next: vscode.ProvideCompletionItemsSignature) => {
// If not in inline template, do not perform request forwarding
Expand Down Expand Up @@ -167,7 +178,15 @@ export class AngularLanguageClient implements vscode.Disposable {
return [...(angularCompletions ?? []), ...(htmlProviderCompletions?.items ?? [])];
}

return angularCompletionsPromise.then(items => provideCompletionItem(document, position, items ?? []));
return angularCompletionsPromise;
},
provideFoldingRanges: async (
document: vscode.LinesTextDocument, context: vscode.FoldingContext,
token: vscode.CancellationToken, next) => {
if (!await this.isInAngularProject(document)) {
return null;
}
return next(document, context, token);
}
}
};
Expand Down Expand Up @@ -232,7 +251,7 @@ export class AngularLanguageClient implements vscode.Disposable {
this.clientOptions,
forceDebug,
);
this.disposables.push(this.client.start());
vscode.services.registerLanguageClient(this.client);
await this.client.onReady();
// Must wait for the client to be ready before registering notification
// handlers.
Expand Down Expand Up @@ -364,7 +383,8 @@ function registerNotificationHandlers(client: vscode.LanguageClient) {
})
client.onNotification(SuggestStrictMode, async (params: SuggestStrictModeParams) => {
const config = vscode.workspace.getConfiguration();
if (config.get('angular.enable-strict-mode-prompt') === false) {
if (config.get('angular.enable-strict-mode-prompt') === false ||
config.get('angular.forceStrictTemplates')) {
return;
}
const openTsConfig = 'Open tsconfig.json';
Expand Down Expand Up @@ -416,7 +436,7 @@ function getProbeLocations(bundled: string): string[] {
* Construct the arguments that's used to spawn the server process.
* @param ctx vscode extension context
*/
function constructArgs(ctx: vscode.ExtensionContext, viewEngine: boolean): string[] {
function constructArgs(ctx: vscode.ExtensionContext): string[] {
const config = vscode.workspace.getConfiguration();
const args: string[] = ['--logToConsole'];

Expand All @@ -429,15 +449,7 @@ function constructArgs(ctx: vscode.ExtensionContext, viewEngine: boolean): strin
}

const ngProbeLocations = getProbeLocations(ctx.extensionPath);
if (viewEngine) {
args.push('--viewEngine');
args.push('--ngProbeLocations', [
path.join(ctx.extensionPath, 'v12_language_service'),
...ngProbeLocations,
].join(','));
} else {
args.push('--ngProbeLocations', ngProbeLocations.join(','));
}
args.push('--ngProbeLocations', ngProbeLocations.join(','));

const includeAutomaticOptionalChainCompletions =
config.get<boolean>('angular.suggest.includeAutomaticOptionalChainCompletions');
Expand All @@ -451,6 +463,18 @@ function constructArgs(ctx: vscode.ExtensionContext, viewEngine: boolean): strin
args.push('--includeCompletionsWithSnippetText');
}

const angularVersions = getAngularVersionsInWorkspace();
// Only disable block syntax if we find angular/core and every one we find does not support block
// syntax
if (angularVersions.size > 0 && Array.from(angularVersions).every(v => v.version.major < 17)) {
args.push('--disableBlockSyntax');
}

const forceStrictTemplates = config.get<boolean>('angular.forceStrictTemplates');
if (forceStrictTemplates) {
args.push('--forceStrictTemplates');
}

const tsdk: string|null = config.get('typescript.tsdk', null);
const tsProbeLocations = [tsdk, ...getProbeLocations(ctx.extensionPath)];
args.push('--tsProbeLocations', tsProbeLocations.join(','));
Expand All @@ -466,35 +490,19 @@ function getServerOptions(ctx: vscode.ExtensionContext, debug: boolean): vscode.
NG_DEBUG: true,
};

// Because the configuration is typed as "boolean" in package.json, vscode
// will return false even when the value is not set. If value is false, then
// we need to check if all projects support Ivy language service.
const config = vscode.workspace.getConfiguration();
let viewEngine: boolean = config.get('angular.view-engine') || !allProjectsSupportIvy();
if (viewEngine && !allProjectsSupportVE()) {
viewEngine = false;
if (config.get('angular.view-engine')) {
vscode.window.showErrorMessage(
`The legacy View Engine option is enabled but the workspace contains a version 13 Angular project.` +
` Legacy View Engine will be disabled since support for it was dropped in v13.`,
);
} else if (!allProjectsSupportIvy() && !allProjectsSupportVE()) {
vscode.window.showErrorMessage(
`The workspace contains a project that does not support legacy View Engine (Angular v13+) and a project that does not support the new current runtime (v8 and below).` +
`The extension will not work for the legacy project in this workspace.`);
}
}

// Node module for the language server
const args = constructArgs(ctx, viewEngine);
const args = constructArgs(ctx);
const prodBundle = ctx.asAbsolutePath(path.join('node_modules', '@angular', 'language-server'));
const devBundle = ctx.asAbsolutePath(path.join('node_modules', '@angular', 'language-server'));
// VS Code Insider launches extensions in debug mode by default but users
// install prod bundle so we have to check whether dev bundle exists.
const latestServerModule = debug && fs.existsSync(devBundle) ? devBundle : prodBundle;
const v12ServerModule = ctx.asAbsolutePath(
path.join('node_modules', 'v12_language_service', 'node_modules', '@angular', 'language-server'));
const module = viewEngine ? v12ServerModule : latestServerModule;

if (!extensionVersionCompatibleWithAllProjects(latestServerModule)) {
vscode.window.showWarningMessage(
`A project in the workspace is using a newer version of Angular than the language service extension. ` +
`This may cause the extension to show incorrect diagnostics.`);
}

// Argv options for Node.js
const prodExecArgv: string[] = [];
Expand All @@ -508,7 +516,7 @@ function getServerOptions(ctx: vscode.ExtensionContext, debug: boolean): vscode.
return {
// VS Code Insider launches extensions in debug mode by default but users
// install prod bundle so we have to check whether dev bundle exists.
module,
module: latestServerModule,
transport: vscode.TransportKind.ipc,
args,
options: {
Expand All @@ -518,24 +526,38 @@ function getServerOptions(ctx: vscode.ExtensionContext, debug: boolean): vscode.
};
}

function allProjectsSupportIvy() {
function extensionVersionCompatibleWithAllProjects(serverModuleLocation: string): boolean {
const languageServiceVersion =
resolve('@angular/language-service', serverModuleLocation)?.version;
if (languageServiceVersion === undefined) {
return true;
}

const workspaceFolders = vscode.workspace.workspaceFolders || [];
for (const workspaceFolder of workspaceFolders) {
const angularCore = resolve('@angular/core', vscode.Uri.parse(workspaceFolder.uri).fsPath);
if (angularCore?.version.greaterThanOrEqual(new Version('9')) === false) {
if (angularCore === undefined) {
continue;
}
if (!languageServiceVersion.greaterThanOrEqual(angularCore.version, 'minor')) {
return false;
}
}
return true;
}

function allProjectsSupportVE() {
/**
* Returns true if any project in the workspace supports block syntax (v17+).
*/
function getAngularVersionsInWorkspace(): Set<NodeModule> {
const angularCoreModules = new Set<NodeModule>();
const workspaceFolders = vscode.workspace.workspaceFolders || [];
for (const workspaceFolder of workspaceFolders) {
const angularCore = resolve('@angular/core', vscode.Uri.parse(workspaceFolder.uri).fsPath);
if (angularCore?.version.greaterThanOrEqual(new Version('13')) === true) {
return false;
if (angularCore === undefined) {
continue;
}
angularCoreModules.add(angularCore);
}
return true;
return angularCoreModules;
}
Loading