Skip to content
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
4 changes: 4 additions & 0 deletions packages/ng-helper-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { registerHover } from './features/hover';
import { supportInlineHtml } from './features/inlineHtml';
import { registerLink } from './features/link';
import { registerSemantic } from './features/semantic';
import { registerSignatureHelp } from './features/signatureHelp';
import { registerStatusBar } from './features/statusBar';
import { NgContext } from './ngContext';
import { StateControl } from './service/stateControl';
Expand Down Expand Up @@ -43,6 +44,9 @@ export async function activate(vscodeContext: ExtensionContext) {
// completion
registerCompletion(ngContext);

// signature help
registerSignatureHelp(ngContext);

// hover
registerHover(ngContext);

Expand Down
8 changes: 1 addition & 7 deletions packages/ng-helper-vscode/src/features/completion/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,7 @@ function buildCompletionList(res: NgTypeInfo[]) {
: CompletionItemKind.Field,
);

if (x.isFunction) {
// 分两段补全,第一段是函数名,第二段是参数
let snippet = `${x.name}$1(`;
snippet += x.paramNames!.map((x, i) => `\${${i + 2}:${x}}`).join(', ');
snippet += ')';
item.insertText = new SnippetString(snippet);
} else if (x.isFilter && x.paramNames?.length) {
if (x.isFilter && x.paramNames?.length) {
let snippet = x.name + SPACE;
snippet += x.paramNames.map((x, i) => `:\${${i + 1}:${x}}`).join(' ');
item.insertText = new SnippetString(snippet);
Expand Down
40 changes: 38 additions & 2 deletions packages/ng-helper-vscode/src/features/inlineHtml/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import {
type CompletionList,
type Definition,
type Hover,
type SignatureHelp,
type TextDocument,
} from 'vscode';

import type { NgContext } from '../../ngContext';
import { triggerChars } from '../completion';
import { triggerChars as completionTriggerChars } from '../completion';
import { htmlSemanticProvider, legend } from '../semantic';
import { triggerChars as signatureHelpTriggerChars } from '../signatureHelp';
import { getOriginalFileName } from '../utils';

import { resolveVirtualDocText } from './utils';
Expand All @@ -34,6 +36,7 @@ export function supportInlineHtml(ngContext: NgContext) {
requestForwardHover(ngContext);
requestForwardDefinition(ngContext);
requestForwardCompletion(ngContext);
requestForwardSignatureHelp(ngContext);
}

function providerSemantic(ngContext: NgContext) {
Expand Down Expand Up @@ -154,7 +157,40 @@ function requestForwardCompletion(ngContext: NgContext) {
return info;
},
},
...triggerChars,
...completionTriggerChars,
),
);
}

function requestForwardSignatureHelp(ngContext: NgContext) {
ngContext.vscodeContext.subscriptions.push(
languages.registerSignatureHelpProvider(
[
// 这个只有 ts 才能起作用,js 没有类型信息
{ scheme: 'file', language: 'typescript' },
],
{
async provideSignatureHelp(document, position, _, ctx) {
if (!ngContext.isNgProjectDocument(document)) {
return;
}

const vDocText = resolveVirtualDocText(document, position);
if (!vDocText) {
return;
}

const vDocUri = prepareVirtualDocument(document, vDocText);
const info = await commands.executeCommand<SignatureHelp>(
'vscode.executeSignatureHelpProvider',
vDocUri,
position,
ctx.triggerCharacter,
);
return info;
},
},
...signatureHelpTriggerChars,
),
);
}
Expand Down
158 changes: 158 additions & 0 deletions packages/ng-helper-vscode/src/features/signatureHelp/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { getCursorAtInfo, type CursorAtAttrValueInfo, type CursorAtTemplateInfo } from '@ng-helper/shared/lib/cursorAt';
import { getActiveParameterIndex, getFnCallNode } from '@ng-helper/shared/lib/fnCallNgSyntax';
import type { NgHoverInfo } from '@ng-helper/shared/lib/plugin';
import {
languages,
SignatureHelp,
SignatureInformation,
ParameterInformation,
type TextDocument,
type Position,
type CancellationToken,
} from 'vscode';

import { checkCancellation, createCancellationTokenSource, withTimeoutAndMeasure } from '../../asyncUtils';
import type { NgContext } from '../../ngContext';
import { buildCursor, normalizePath } from '../../utils';
import { onTypeHover } from '../hover/utils';
import { getControllerNameInfo, isComponentHtml } from '../utils';

export const triggerChars = ['(', ','];

export function registerSignatureHelp(ngContext: NgContext): void {
ngContext.vscodeContext.subscriptions.push(
languages.registerSignatureHelpProvider(
'html',
{
async provideSignatureHelp(document, position, token, _context): Promise<SignatureHelp | undefined> {
if (!ngContext.isNgProjectDocument(document)) {
return;
}

const cancelTokenSource = createCancellationTokenSource(token);
return await withTimeoutAndMeasure(
'provideSignatureHelp',
() => provideSignatureHelp({ document, position, cancelToken: token, ngContext }),
{ cancelTokenSource },
);
},
},
...triggerChars,
),
);
}

async function provideSignatureHelp({
document,
position,
ngContext,
cancelToken,
}: {
document: TextDocument;
position: Position;
ngContext: NgContext;
cancelToken: CancellationToken;
}) {
const cursorAtInfo = getCursorAtInfo(document.getText(), buildCursor(document, position), {
filePath: normalizePath(document.uri.fsPath), // 注意:这里的处理方式要一致,否则缓存会失效
version: document.version,
});

if (cursorAtInfo.type !== 'template' && cursorAtInfo.type !== 'attrValue') {
return;
}

const cursorAt = cursorAtInfo.relativeCursorAt;
const ngExprStr = cursorAtInfo.type === 'template' ? cursorAtInfo.template : cursorAtInfo.attrValue;
const callNode = getFnCallNode(ngExprStr, cursorAt);
if (!callNode) {
return;
}

const activeParameterIndex = getActiveParameterIndex(callNode, cursorAt);
if (activeParameterIndex === -1) {
return;
}

const newCursorAtInfo = { ...cursorAtInfo };
// 将光标移到函数名字上
newCursorAtInfo.relativeCursorAt -= cursorAt - (callNode.callee.end - 1);

const hoverInfo = await getMethodHoverInfo({
document,
ngContext,
cursorAtInfo: newCursorAtInfo,
cancelToken,
});
if (!hoverInfo) {
return;
}

checkCancellation(cancelToken);

return buildSignatureHelp(hoverInfo, activeParameterIndex);
}

async function getMethodHoverInfo({
document,
ngContext,
cursorAtInfo,
cancelToken,
}: {
document: TextDocument;
cursorAtInfo: CursorAtAttrValueInfo | CursorAtTemplateInfo;
ngContext: NgContext;
cancelToken: CancellationToken;
}) {
return await onTypeHover({
type: 'hover',
document,
cursorAtInfo,
// eslint-disable-next-line
onHoverFilterName: async () => undefined,
onHoverLocalType: () => undefined,
onHoverType: async (scriptFilePath, contextString, cursorAt, hoverPropName) => {
checkCancellation(cancelToken);

if (isComponentHtml(document)) {
return await ngContext.rpcApi.getComponentTypeHoverApi({
cancelToken,
params: { fileName: scriptFilePath, contextString, cursorAt, hoverPropName },
});
}

const ctrlInfo = getControllerNameInfo(cursorAtInfo.context);
if (ctrlInfo) {
return await ngContext.rpcApi.getControllerTypeHoverApi({
cancelToken,
params: { fileName: scriptFilePath, contextString, cursorAt, hoverPropName, ...ctrlInfo },
});
}
},
});
}

function buildSignatureHelp(hoverInfo: NgHoverInfo, activeParameterIndex: number): SignatureHelp | undefined {
if (!hoverInfo.isMethod) {
return;
}

// remove '(method) ' from start
hoverInfo.formattedTypeString = hoverInfo.formattedTypeString.slice('(method) '.length);

const signature = new SignatureInformation(hoverInfo.formattedTypeString, hoverInfo.document);
signature.parameters =
hoverInfo.parameters?.map((p) => {
const paramStr = `${p.name}: ${p.typeString}`;
const start = hoverInfo.formattedTypeString.indexOf(paramStr);
const end = start + paramStr.length;
return new ParameterInformation([start, end], p.document);
}) ?? [];

const sigHelp = new SignatureHelp();
sigHelp.signatures = [signature];
sigHelp.activeParameter = activeParameterIndex;
sigHelp.activeSignature = 0; // 目前不考虑重载

return sigHelp;
}
Original file line number Diff line number Diff line change
Expand Up @@ -1132,10 +1132,7 @@ Array [
"detail": "(method) onDragDrop: (p: { dragDropTarget: DragDropTarget; }) => void",
"documentation": undefined,
"filterText": undefined,
"insertText": t {
"e": 1,
"value": "onDragDrop$1(\${2:p})",
},
"insertText": "onDragDrop",
"kind": "Method",
"label": "onDragDrop",
"preselect": undefined,
Expand All @@ -1153,6 +1150,17 @@ Array [
"sortText": "004",
"textEdit": undefined,
},
Object {
"detail": "(method) fmt: (s: string, option: { lower: boolean; upper: boolean; }) => string",
"documentation": undefined,
"filterText": undefined,
"insertText": "fmt",
"kind": "Method",
"label": "fmt",
"preselect": undefined,
"sortText": "005",
"textEdit": undefined,
},
]
`;

Expand Down
Loading