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
8 changes: 5 additions & 3 deletions packages/language-server/src/plugins/css/CSSDocument.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Stylesheet, TextDocument } from 'vscode-css-languageservice';
import { Position } from 'vscode-languageserver';
import { getLanguageService } from './service';
import { Document, DocumentMapper, ReadableDocument, TagInformation } from '../../lib/documents';
import { CSSLanguageServices, getLanguageService } from './service';

export interface CSSDocumentBase extends DocumentMapper, TextDocument {
languageId: string;
Expand All @@ -15,7 +15,7 @@ export class CSSDocument extends ReadableDocument implements DocumentMapper {
public stylesheet: Stylesheet;
public languageId: string;

constructor(private parent: Document) {
constructor(private parent: Document, languageServices: CSSLanguageServices) {
super();

if (this.parent.styleInfo) {
Expand All @@ -29,7 +29,9 @@ export class CSSDocument extends ReadableDocument implements DocumentMapper {
}

this.languageId = this.language;
this.stylesheet = getLanguageService(this.language).parseStylesheet(this);
this.stylesheet = getLanguageService(languageServices, this.languageId).parseStylesheet(
this
);
}

/**
Expand Down
63 changes: 41 additions & 22 deletions packages/language-server/src/plugins/css/CSSPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
SymbolInformation,
CompletionItem,
CompletionItemKind,
SelectionRange
SelectionRange,
WorkspaceFolder
} from 'vscode-languageserver';
import {
Document,
Expand All @@ -38,11 +39,12 @@ import {
SelectionRangeProvider
} from '../interfaces';
import { CSSDocument, CSSDocumentBase } from './CSSDocument';
import { getLanguage, getLanguageService } from './service';
import { CSSLanguageServices, getLanguage, getLanguageService } from './service';
import { GlobalVars } from './global-vars';
import { getIdClassCompletion } from './features/getIdClassCompletion';
import { AttributeContext, getAttributeContextAtPosition } from '../../lib/documents/parseHtml';
import { StyleAttributeDocument } from './StyleAttributeDocument';
import { getDocumentContext } from '../documentContext';

export class CSSPlugin
implements
Expand All @@ -57,10 +59,19 @@ export class CSSPlugin
__name = 'css';
private configManager: LSConfigManager;
private cssDocuments = new WeakMap<Document, CSSDocument>();
private cssLanguageServices: CSSLanguageServices;
private workspaceFolders: WorkspaceFolder[];
private triggerCharacters = ['.', ':', '-', '/'];
private globalVars = new GlobalVars();

constructor(docManager: DocumentManager, configManager: LSConfigManager) {
constructor(
docManager: DocumentManager,
configManager: LSConfigManager,
workspaceFolders: WorkspaceFolder[],
cssLanguageServices: CSSLanguageServices
) {
this.cssLanguageServices = cssLanguageServices;
this.workspaceFolders = workspaceFolders;
this.configManager = configManager;
this.updateConfigs();

Expand All @@ -71,7 +82,7 @@ export class CSSPlugin
});

docManager.on('documentChange', (document) =>
this.cssDocuments.set(document, new CSSDocument(document))
this.cssDocuments.set(document, new CSSDocument(document, this.cssLanguageServices))
);
docManager.on('documentClose', (document) => this.cssDocuments.delete(document));
}
Expand All @@ -82,7 +93,7 @@ export class CSSPlugin
}

const cssDocument = this.getCSSDoc(document);
const [range] = getLanguageService(extractLanguage(cssDocument)).getSelectionRanges(
const [range] = this.getLanguageService(extractLanguage(cssDocument)).getSelectionRanges(
cssDocument,
[cssDocument.getGeneratedPosition(position)],
cssDocument.stylesheet
Expand All @@ -107,7 +118,7 @@ export class CSSPlugin
return [];
}

return getLanguageService(kind)
return this.getLanguageService(kind)
.doValidation(cssDocument, cssDocument.stylesheet)
.map((diagnostic) => ({ ...diagnostic, source: getLanguage(kind) }))
.map((diagnostic) => mapObjWithRangeToOriginal(cssDocument, diagnostic));
Expand All @@ -131,25 +142,28 @@ export class CSSPlugin
this.inStyleAttributeWithoutInterpolation(attributeContext, document.getText())
) {
const [start, end] = attributeContext.valueRange;
return this.doHoverInternal(new StyleAttributeDocument(document, start, end), position);
return this.doHoverInternal(
new StyleAttributeDocument(document, start, end, this.cssLanguageServices),
position
);
}

return null;
}
private doHoverInternal(cssDocument: CSSDocumentBase, position: Position) {
const hoverInfo = getLanguageService(extractLanguage(cssDocument)).doHover(
const hoverInfo = this.getLanguageService(extractLanguage(cssDocument)).doHover(
cssDocument,
cssDocument.getGeneratedPosition(position),
cssDocument.stylesheet
);
return hoverInfo ? mapHoverToParent(cssDocument, hoverInfo) : hoverInfo;
}

getCompletions(
async getCompletions(
document: Document,
position: Position,
completionContext?: CompletionContext
): CompletionList | null {
): Promise<CompletionList | null> {
const triggerCharacter = completionContext?.triggerCharacter;
const triggerKind = completionContext?.triggerKind;
const isCustomTriggerCharacter = triggerKind === CompletionTriggerKind.TriggerCharacter;
Expand Down Expand Up @@ -182,7 +196,7 @@ export class CSSPlugin
return this.getCompletionsInternal(
document,
position,
new StyleAttributeDocument(document, start, end)
new StyleAttributeDocument(document, start, end, this.cssLanguageServices)
);
} else {
return getIdClassCompletion(cssDocument, attributeContext);
Expand All @@ -200,7 +214,7 @@ export class CSSPlugin
);
}

private getCompletionsInternal(
private async getCompletionsInternal(
document: Document,
position: Position,
cssDocument: CSSDocumentBase
Expand All @@ -219,7 +233,7 @@ export class CSSPlugin
return null;
}

const lang = getLanguageService(type);
const lang = this.getLanguageService(type);
let emmetResults: CompletionList = {
isIncomplete: false,
items: []
Expand Down Expand Up @@ -256,10 +270,11 @@ export class CSSPlugin
]);
}

const results = lang.doComplete(
const results = await lang.doComplete2(
cssDocument,
cssDocument.getGeneratedPosition(position),
cssDocument.stylesheet
cssDocument.stylesheet,
getDocumentContext(cssDocument.uri, this.workspaceFolders)
);
return CompletionList.create(
this.appendGlobalVars(
Expand Down Expand Up @@ -301,7 +316,7 @@ export class CSSPlugin
return [];
}

return getLanguageService(extractLanguage(cssDocument))
return this.getLanguageService(extractLanguage(cssDocument))
.findDocumentColors(cssDocument, cssDocument.stylesheet)
.map((colorInfo) => mapObjWithRangeToOriginal(cssDocument, colorInfo));
}
Expand All @@ -319,7 +334,7 @@ export class CSSPlugin
return [];
}

return getLanguageService(extractLanguage(cssDocument))
return this.getLanguageService(extractLanguage(cssDocument))
.getColorPresentations(
cssDocument,
cssDocument.stylesheet,
Expand All @@ -340,7 +355,7 @@ export class CSSPlugin
return [];
}

return getLanguageService(extractLanguage(cssDocument))
return this.getLanguageService(extractLanguage(cssDocument))
.findDocumentSymbols(cssDocument, cssDocument.stylesheet)
.map((symbol) => {
if (!symbol.containerName) {
Expand All @@ -359,16 +374,16 @@ export class CSSPlugin
private getCSSDoc(document: Document) {
let cssDoc = this.cssDocuments.get(document);
if (!cssDoc || cssDoc.version < document.version) {
cssDoc = new CSSDocument(document);
cssDoc = new CSSDocument(document, this.cssLanguageServices);
this.cssDocuments.set(document, cssDoc);
}
return cssDoc;
}

private updateConfigs() {
getLanguageService('css')?.configure(this.configManager.getCssConfig());
getLanguageService('scss')?.configure(this.configManager.getScssConfig());
getLanguageService('less')?.configure(this.configManager.getLessConfig());
this.getLanguageService('css')?.configure(this.configManager.getCssConfig());
this.getLanguageService('scss')?.configure(this.configManager.getScssConfig());
this.getLanguageService('less')?.configure(this.configManager.getLessConfig());
}

private featureEnabled(feature: keyof LSCSSConfig) {
Expand All @@ -377,6 +392,10 @@ export class CSSPlugin
this.configManager.enabled(`css.${feature}.enable`)
);
}

private getLanguageService(kind: string) {
return getLanguageService(this.cssLanguageServices, kind);
}
}

function shouldExcludeValidation(kind?: string) {
Expand Down
94 changes: 94 additions & 0 deletions packages/language-server/src/plugins/css/FileSystemProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { stat, readdir, Stats } from 'fs';
import { promisify } from 'util';
import {
FileStat,
FileSystemProvider as CSSFileSystemProvider,
FileType
} from 'vscode-css-languageservice';
import { urlToPath } from '../../utils';

interface StatLike {
isDirectory(): boolean;
isFile(): boolean;
isSymbolicLink(): boolean;
}

export class FileSystemProvider implements CSSFileSystemProvider {
// TODO use fs/promises after we bumps the target nodejs versions
private promisifyStat = promisify(stat);
private promisifyReaddir = promisify(readdir);

constructor() {
this.readDirectory = this.readDirectory.bind(this);
this.stat = this.stat.bind(this);
}

async stat(uri: string): Promise<FileStat> {
const path = urlToPath(uri);

if (!path) {
return this.unknownStat();
}

let stat: Stats;
try {
stat = await this.promisifyStat(path);
} catch (error) {
if (
error != null &&
typeof error === 'object' &&
'code' in error &&
(error as { code: string }).code === 'ENOENT'
) {
return {
type: FileType.Unknown,
ctime: -1,
mtime: -1,
size: -1
};
}

throw error;
}

return {
ctime: stat.ctimeMs,
mtime: stat.mtimeMs,
size: stat.size,
type: this.getFileType(stat)
};
}

private unknownStat(): FileStat {
return {
type: FileType.Unknown,
ctime: -1,
mtime: -1,
size: -1
};
}

private getFileType(stat: StatLike) {
return stat.isDirectory()
? FileType.Directory
: stat.isFile()
? FileType.File
: stat.isSymbolicLink()
? FileType.SymbolicLink
: FileType.Unknown;
}

async readDirectory(uri: string): Promise<Array<[string, FileType]>> {
const path = urlToPath(uri);

if (!path) {
return [];
}

const files = await this.promisifyReaddir(path, {
withFileTypes: true
});

return files.map((file) => [file.name, this.getFileType(file)]);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Stylesheet } from 'vscode-css-languageservice';
import { Position } from 'vscode-languageserver';
import { getLanguageService } from './service';
import { CSSLanguageServices, getLanguageService } from './service';
import { Document, DocumentMapper, ReadableDocument } from '../../lib/documents';

const PREFIX = '__ {';
Expand All @@ -15,11 +15,12 @@ export class StyleAttributeDocument extends ReadableDocument implements Document
constructor(
private readonly parent: Document,
private readonly attrStart: number,
private readonly attrEnd: number
private readonly attrEnd: number,
languageServices: CSSLanguageServices
) {
super();

this.stylesheet = getLanguageService(this.languageId).parseStylesheet(this);
this.stylesheet = getLanguageService(languageServices).parseStylesheet(this);
}

/**
Expand Down
Loading