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
36 changes: 36 additions & 0 deletions server/src/core/findHover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Hover, MarkupKind } from "vscode-languageserver/node";

import { Selector, StylesheetMap } from "../types";
import { findSymbols } from "./findDefinition";

const MAX_LINES = 20;

export function findHover(
selector: Selector,
stylesheetMap: StylesheetMap,
options?: { peekVariables?: boolean }
): Hover | null {
const symbols = findSymbols(selector, stylesheetMap, options);
if (symbols.length === 0) {
return null;
}

const symbol = symbols[0];
const styleSheet = stylesheetMap[symbol.location.uri];
if (!styleSheet) {
return null;
}

const source = styleSheet.document.getText(symbol.location.range);
const lines = source.split(/\r?\n/);
const truncated = lines.length > MAX_LINES;
const snippet = (truncated ? lines.slice(0, MAX_LINES) : lines).join("\n");
const suffix = truncated ? "\n/* … */" : "";

return {
contents: {
kind: MarkupKind.Markdown,
value: "```css\n" + snippet + suffix + "\n```",
},
};
}
28 changes: 28 additions & 0 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
TextDocumentSyncKind,
TextDocumentPositionParams,
Definition,
Hover,
InitializeParams,
DidChangeConfigurationNotification,
} from "vscode-languageserver/node";
Expand All @@ -22,6 +23,7 @@ import {
findDefinition,
isLanguageServiceSupported,
} from "./core/findDefinition";
import { findHover } from "./core/findHover";
import { create } from "./logger";

// Creates the LSP connection
Expand Down Expand Up @@ -119,6 +121,7 @@ connection.onInitialize((params) => {
change: TextDocumentSyncKind.Full,
},
definitionProvider: true,
hoverProvider: true,
workspaceSymbolProvider: true,
},
};
Expand Down Expand Up @@ -233,6 +236,31 @@ connection.onDefinition(
}
);

connection.onHover(
async (
textDocumentPositon: TextDocumentPositionParams
): Promise<Hover | null> => {
const documentIdentifier = textDocumentPositon.textDocument;
const position = textDocumentPositon.position;

const document = documents.get(documentIdentifier.uri);

if (!document || !(await isValidPeekSource(document))) {
return null;
}
const settings = await getDocumentSettings(document.uri);

const selector: Selector = findSelector(document, position, settings);
if (!selector) {
return null;
}

return findHover(selector, styleSheets, {
peekVariables: settings.peekVariables,
});
}
);

connection.onWorkspaceSymbol(async ({ query }) => {
if (query.length < 2) return [];
const selectors: Selector[] = [
Expand Down
65 changes: 65 additions & 0 deletions tests/src/findHover.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as assert from "assert";
import * as vscode from "vscode";
import { TextDocument as ServerTextDocument } from "vscode-languageserver";

import { findHover } from "../../server/out/core/findHover";
import { create } from "../../server/out/logger";
import type { StylesheetMap, Selector } from "../../server/src/types";

async function loadStylesheets(files: string[]): Promise<StylesheetMap> {
const map: StylesheetMap = {};
for (const file of files) {
const vscodeDoc = await vscode.workspace.openTextDocument(
vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, file)
);
const text = vscodeDoc.getText();
const serverDoc = ServerTextDocument.create(
vscodeDoc.uri.toString(),
vscodeDoc.languageId,
vscodeDoc.version,
text
);
map[serverDoc.uri] = { document: serverDoc };
}
return map;
}

suite("findHover", () => {
create(console as any);
let map: StylesheetMap;
suiteSetup(async () => {
map = await loadStylesheets(["stylesheet.css", "example.scss"]);
});

test("returns markdown hover with css source for a class selector", () => {
const selector: Selector = { attribute: "class", value: "test" };
const hover = findHover(selector, map);
assert.ok(hover, "expected a hover result");
const contents = hover!.contents as { kind: string; value: string };
assert.strictEqual(contents.kind, "markdown");
assert.match(contents.value, /^```css\n/);
assert.match(contents.value, /\n```$/);
assert.ok(
contents.value.includes(".test"),
`expected hover to include the selector body, got: ${contents.value}`
);
});

test("returns markdown hover with css source for an id selector", () => {
const selector: Selector = { attribute: "id", value: "testID" };
const hover = findHover(selector, map);
assert.ok(hover, "expected a hover result");
const contents = hover!.contents as { kind: string; value: string };
assert.ok(contents.value.includes("#testID"));
assert.ok(contents.value.includes("color: green"));
});

test("returns null when no matching selector exists", () => {
const selector: Selector = {
attribute: "class",
value: "this-class-does-not-exist-anywhere",
};
const hover = findHover(selector, map);
assert.strictEqual(hover, null);
});
});
Loading