Skip to content

Commit

Permalink
fix: even more textEdit work
Browse files Browse the repository at this point in the history
  • Loading branch information
wkillerud committed Sep 14, 2024
1 parent c5724bf commit 39909c0
Showing 1 changed file with 190 additions and 93 deletions.
283 changes: 190 additions & 93 deletions packages/language-services/src/features/do-complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ export class DoComplete extends LanguageFeature {
const items = await this.doMixinCompletion(
document,
currentDocument,
currentWord,
context,
symbol,
isPrivate,
);
Expand Down Expand Up @@ -779,10 +779,9 @@ export class DoComplete extends LanguageFeature {
const mixs = await this.doMixinCompletion(
document,
currentDocument,
context.currentWord,
context,
symbol,
isPrivate,
context.namespace,
prefix,
);
if (mixs.length > 0) {
Expand Down Expand Up @@ -989,44 +988,25 @@ export class DoComplete extends LanguageFeature {
private async doMixinCompletion(
initialDocument: TextDocument,
currentDocument: TextDocument,
currentWord: string,
context: CompletionContext,
symbol: SassDocumentSymbol,
isPrivate: boolean,
namespace = "",
prefix = "",
): Promise<CompletionItem[]> {
const items: CompletionItem[] = [];
const snippetSupport =
this.clientCapabilities.textDocument?.completion?.completionItem
?.snippetSupport;

const label = `${prefix}${symbol.name}`;

const { namespace } = context;
const filterText = namespace
? namespace !== "*"
? `${namespace}.${prefix}${symbol.name}`
: `${prefix}${symbol.name}`
: symbol.name;

const isEmbedded = this.isEmbedded(initialDocument);

const noDot =
namespace === "*" ||
isEmbedded ||
initialDocument.languageId === "sass" ||
this.configuration.completionSettings?.afterModule === "";

let insertText = namespace
? noDot
? `${prefix}${symbol.name}`
: `.${prefix}${symbol.name}`
: symbol.name;

if (
namespace &&
namespace !== "*" &&
this.configuration.completionSettings?.afterModule &&
this.configuration.completionSettings.afterModule.startsWith("{module}")
) {
insertText = `${namespace}${insertText}`;
}

const sortText = isPrivate ? label.replace(/^$[_]/, "") : undefined;

const documentation = {
Expand All @@ -1039,90 +1019,207 @@ export class DoComplete extends LanguageFeature {
}
documentation.value += `\n____\nMixin declared in ${this.getFileName(currentDocument.uri)}`;

const getCompletionVariants = (
insertText: string,
detail?: string,
): CompletionItem[] => {
const variants: CompletionItem[] = [];
// Not all mixins have @content, but when they do, be smart about adding brackets
// and move the cursor to be ready to add said contents.
// Include as separate suggestion since content may not always be needed or wanted.
if (
this.clientCapabilities.textDocument?.completion?.completionItem
?.insertReplaceSupport
) {
const base: CompletionItem = {
label,
documentation,
filterText,
sortText,
kind: CompletionItemKind.Method,
insertTextFormat: snippetSupport
? InsertTextFormat.Snippet
: InsertTextFormat.PlainText,
tags: symbol.sassdoc?.deprecated ? [CompletionItemTag.Deprecated] : [],
};

let detail: string | undefined;
let insert = label;

if (
this.configuration.completionSettings?.suggestionStyle !== "bracket"
) {
variants.push({
documentation,
filterText,
kind: CompletionItemKind.Method,
label,
labelDetails: detail ? { detail: `(${detail})` } : undefined,
insertText,
insertTextFormat: InsertTextFormat.Snippet,
sortText,
tags: symbol.sassdoc?.deprecated
? [CompletionItemTag.Deprecated]
: [],
});
const makeCompletionVariants = () => {
// Not all mixins have @content, but when they do, be smart about adding brackets
// and move the cursor to be ready to add said contents.
// Include as separate suggestion since content may not always be needed or wanted.
if (
this.configuration.completionSettings?.suggestionStyle !== "bracket"
) {
items.push({
...base,
labelDetails: detail ? { detail: `(${detail})` } : undefined,
textEdit: TextEdit.replace(
this.getInsertRange(context, insert),
insert,
),
});
}

if (
snippetSupport &&
this.configuration.completionSettings?.suggestionStyle !==
"nobracket" &&
currentDocument.languageId === "scss"
) {
// TODO: test if this works correctly with multiline
const insertSnippet = `${insert} {\n\t$0\n}`;
items.push({
...base,
labelDetails: { detail: detail ? `(${detail}) { }` : " { }" },
textEdit: TextEdit.replace(
this.getInsertRange(context, insertSnippet),
insertSnippet,
),
});
}
};

// In the case of no required parameters, skip details.
if (symbol.detail && snippetSupport) {
const parameters = getParametersFromDetail(symbol.detail);
const requiredParameters = parameters.filter((p) => !p.defaultValue);

// If there are required parameters, add a suggestion with only them.
if (requiredParameters.length > 0) {
const parametersSnippet = requiredParameters
.map((p, i) => mapParameterSnippet(p, i, symbol.sassdoc))
.join(", ");
insert = label + `(${parametersSnippet})`;

detail = requiredParameters
.map((p) => mapParameterSignature(p))
.join(", ");
}

// If there are optional parameters, add a suggestion with all parameters.
if (requiredParameters.length !== parameters.length) {
const parametersSnippet = parameters
.map((p, i) => mapParameterSnippet(p, i, symbol.sassdoc))
.join(", ");
insert = label + `(${parametersSnippet})`;
detail = parameters.map((p) => mapParameterSignature(p)).join(", ");

makeCompletionVariants();
}
} else {
makeCompletionVariants();
}

return items;
} else {
// This method is kept for backwards compatibility.
// It might be removed or simplified in a major version, placing
// the responsibility for correctly treating the word boundary
// on the editor.
// If editors are having trouble with the completion items made
// by this code, they should update to support completion items
// with text edits added in 3.16 of the LSP spec.

const isEmbedded = this.isEmbedded(initialDocument);
const noDot =
namespace === "*" ||
isEmbedded ||
initialDocument.languageId === "sass" ||
this.configuration.completionSettings?.afterModule === "";

let insertText = namespace
? noDot
? `${prefix}${symbol.name}`
: `.${prefix}${symbol.name}`
: symbol.name;

if (
this.configuration.completionSettings?.suggestionStyle !==
"nobracket" &&
currentDocument.languageId === "scss"
namespace &&
namespace !== "*" &&
this.configuration.completionSettings?.afterModule &&
this.configuration.completionSettings.afterModule.startsWith("{module}")
) {
variants.push({
insertText = `${namespace}${insertText}`;
}

const getCompletionVariants = (
insertText: string,
detail?: string,
): CompletionItem[] => {
const variants: CompletionItem[] = [];
const base: CompletionItem = {
documentation,
filterText,
kind: CompletionItemKind.Method,
label,
labelDetails: { detail: detail ? `(${detail}) { }` : " { }" },
insertText: (insertText += " {\n\t$0\n}"),
insertTextFormat: InsertTextFormat.Snippet,
insertTextFormat: snippetSupport
? InsertTextFormat.Snippet
: InsertTextFormat.PlainText,
sortText,
tags: symbol.sassdoc?.deprecated
? [CompletionItemTag.Deprecated]
: [],
});
}
};

return variants;
};
// Not all mixins have @content, but when they do, be smart about adding brackets
// and move the cursor to be ready to add said contents.
// Include as separate suggestion since content may not always be needed or wanted.
if (
this.configuration.completionSettings?.suggestionStyle !== "bracket"
) {
variants.push({
...base,
labelDetails: detail ? { detail: `(${detail})` } : undefined,
insertText,
});
}

// In the case of no required parameters, skip details.
// If there are required parameters, add a suggestion with only them.
// If there are optional parameters, add a suggestion with all parameters.
if (symbol.detail) {
const parameters = getParametersFromDetail(symbol.detail);
const requiredParameters = parameters.filter((p) => !p.defaultValue);
if (requiredParameters.length > 0) {
const parametersSnippet = requiredParameters
.map((p, i) => mapParameterSnippet(p, i, symbol.sassdoc))
.join(", ");
const insert = insertText + `(${parametersSnippet})`;

const detail = requiredParameters
.map((p) => mapParameterSignature(p))
.join(", ");

items.push(...getCompletionVariants(insert, detail));
}
if (requiredParameters.length !== parameters.length) {
const parametersSnippet = parameters
.map((p, i) => mapParameterSnippet(p, i, symbol.sassdoc))
.join(", ");
const insert = insertText + `(${parametersSnippet})`;
if (
snippetSupport &&
this.configuration.completionSettings?.suggestionStyle !==
"nobracket" &&
currentDocument.languageId === "scss"
) {
variants.push({
...base,
labelDetails: { detail: detail ? `(${detail}) { }` : " { }" },
insertText: (insertText += " {\n\t$0\n}"),
});
}

return variants;
};

const detail = parameters
.map((p) => mapParameterSignature(p))
.join(", ");
// In the case of no required parameters, skip details.
// If there are required parameters, add a suggestion with only them.
// If there are optional parameters, add a suggestion with all parameters.
if (symbol.detail && snippetSupport) {
const parameters = getParametersFromDetail(symbol.detail);
const requiredParameters = parameters.filter((p) => !p.defaultValue);
if (requiredParameters.length > 0) {
const parametersSnippet = requiredParameters
.map((p, i) => mapParameterSnippet(p, i, symbol.sassdoc))
.join(", ");
const insert = insertText + `(${parametersSnippet})`;

const detail = requiredParameters
.map((p) => mapParameterSignature(p))
.join(", ");

items.push(...getCompletionVariants(insert, detail));
}
if (requiredParameters.length !== parameters.length) {
const parametersSnippet = parameters
.map((p, i) => mapParameterSnippet(p, i, symbol.sassdoc))
.join(", ");
const insert = insertText + `(${parametersSnippet})`;

const detail = parameters
.map((p) => mapParameterSignature(p))
.join(", ");

items.push(...getCompletionVariants(insert, detail));
items.push(...getCompletionVariants(insert, detail));
}
} else {
items.push(...getCompletionVariants(insertText));
}
} else {
items.push(...getCompletionVariants(insertText));
return items;
}
return items;
}

private async doFunctionCompletion(
Expand Down

0 comments on commit 39909c0

Please sign in to comment.