Skip to content

Commit

Permalink
feat: support hover tooltip for scss (#367)
Browse files Browse the repository at this point in the history
* feat: support media in scss tooltip

* test: hover media query in scss

* fix: type issue

* fix: `toString()` and code style

---------

Co-authored-by: Martin Aeschlimann <[email protected]>
  • Loading branch information
balaji-sivasakthi and aeschli authored Feb 28, 2024
1 parent 3e2d799 commit c17c287
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 73 deletions.
38 changes: 23 additions & 15 deletions src/services/cssHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ export class CSSHover {
private readonly selectorPrinting: SelectorPrinting;
private defaultSettings?: HoverSettings;

constructor(private readonly clientCapabilities: ClientCapabilities | undefined, private readonly cssDataManager: CSSDataManager) {
constructor(
private readonly clientCapabilities: ClientCapabilities | undefined,
private readonly cssDataManager: CSSDataManager,
) {
this.selectorPrinting = new SelectorPrinting(cssDataManager);
}

public configure(settings: HoverSettings | undefined) {
this.defaultSettings = settings;
}


public doHover(document: TextDocument, position: Position, stylesheet: nodes.Stylesheet, settings = this.defaultSettings): Hover | null {
function getRange(node: nodes.Node) {
return Range.create(document.positionAt(node.offset), document.positionAt(node.end));
Expand All @@ -38,14 +40,24 @@ export class CSSHover {
* Build up the hover by appending inner node's information
*/
let hover: Hover | null = null;
let flagOpts: { text: string; isMedia: boolean };

for (let i = 0; i < nodepath.length; i++) {
const node = nodepath[i];

if (node instanceof nodes.Media) {
const regex = /@media[^\{]+/g;
const matches = node.getText().match(regex);
flagOpts = {
isMedia: true,
text: matches?.[0]!,
};
}

if (node instanceof nodes.Selector) {
hover = {
contents: this.selectorPrinting.selectorToMarkedString(<nodes.Selector>node),
range: getRange(node)
contents: this.selectorPrinting.selectorToMarkedString(<nodes.Selector>node, flagOpts!),
range: getRange(node),
};
break;
}
Expand All @@ -57,7 +69,7 @@ export class CSSHover {
if (!startsWith(node.getText(), '@')) {
hover = {
contents: this.selectorPrinting.simpleSelectorToMarkedString(<nodes.SimpleSelector>node),
range: getRange(node)
range: getRange(node),
};
}
break;
Expand All @@ -71,7 +83,7 @@ export class CSSHover {
if (contents) {
hover = {
contents,
range: getRange(node)
range: getRange(node),
};
} else {
hover = null;
Expand All @@ -88,7 +100,7 @@ export class CSSHover {
if (contents) {
hover = {
contents,
range: getRange(node)
range: getRange(node),
};
} else {
hover = null;
Expand All @@ -99,16 +111,13 @@ export class CSSHover {

if (node instanceof nodes.Node && node.type === nodes.NodeType.PseudoSelector) {
const selectorName = node.getText();
const entry =
selectorName.slice(0, 2) === '::'
? this.cssDataManager.getPseudoElement(selectorName)
: this.cssDataManager.getPseudoClass(selectorName);
const entry = selectorName.slice(0, 2) === '::' ? this.cssDataManager.getPseudoElement(selectorName) : this.cssDataManager.getPseudoClass(selectorName);
if (entry) {
const contents = languageFacts.getEntryDescription(entry, this.doesSupportMarkdown(), settings);
if (contents) {
hover = {
contents,
range: getRange(node)
range: getRange(node),
};
} else {
hover = null;
Expand All @@ -118,7 +127,6 @@ export class CSSHover {
}
}


if (hover) {
hover.contents = this.convertContents(hover.contents);
}
Expand All @@ -135,12 +143,12 @@ export class CSSHover {
else if ('kind' in contents) {
return {
kind: 'plaintext',
value: contents.value
value: contents.value,
};
}
// MarkedString[]
else if (Array.isArray(contents)) {
return contents.map(c => {
return contents.map((c) => {
return typeof c === 'string' ? c : c.value;
});
}
Expand Down
55 changes: 23 additions & 32 deletions src/services/selectorPrinting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ import { CSSDataManager } from '../languageFacts/dataManager';
import { Parser } from '../parser/cssParser';

export class Element {

public parent: Element | null = null;
public children: Element[] | null = null;
public attributes: { name: string, value: string; }[] | null = null;
public attributes: { name: string; value: string }[] | null = null;

public findAttribute(name: string): string | null {
if (this.attributes) {
Expand Down Expand Up @@ -111,27 +110,23 @@ export class Element {
}
}

export class RootElement extends Element {

}
export class RootElement extends Element { }

export class LabelElement extends Element {

constructor(label: string) {
super();
this.addAttr('name', label);
}
}

class MarkedStringPrinter {

private result: string[] = [];

constructor(public quote: string) {
// empty
}

public print(element: Element): MarkedString[] {
public print(element: Element, flagOpts?: { isMedia: boolean; text: string }): MarkedString[] {
this.result = [];
if (element instanceof RootElement) {
if (element.children) {
Expand All @@ -140,8 +135,12 @@ class MarkedStringPrinter {
} else {
this.doPrint([element], 0);
}

const value = this.result.join('\n');
let value;
if (flagOpts) {
value = `${flagOpts.text}\n … ` + this.result.join('\n');
} else {
value = this.result.join('\n');
}
return [{ language: 'html', value }];
}

Expand Down Expand Up @@ -198,9 +197,7 @@ class MarkedStringPrinter {
}
}


namespace quotes {

export function ensure(value: string, which: string): string {
return which + remove(value) + which;
}
Expand All @@ -224,7 +221,6 @@ class Specificity {
}

export function toElement(node: nodes.SimpleSelector, parentElement?: Element | null): Element {

let result = new Element();
for (const child of node.getChildren()) {
switch (child.type) {
Expand Down Expand Up @@ -324,16 +320,13 @@ function unescape(content: string) {
return content;
}


export class SelectorPrinting {
constructor(private cssDataManager: CSSDataManager) {

}
constructor(private cssDataManager: CSSDataManager) { }

public selectorToMarkedString(node: nodes.Selector): MarkedString[] {
public selectorToMarkedString(node: nodes.Selector, flagOpts?: { isMedia: boolean; text: string }): MarkedString[] {
const root = selectorToElement(node);
if (root) {
const markedStrings = new MarkedStringPrinter('"').print(root);
const markedStrings = new MarkedStringPrinter('"').print(root, flagOpts);
markedStrings.push(this.selectorToSpecificityMarkedString(node));
return markedStrings;
} else {
Expand All @@ -355,7 +348,7 @@ export class SelectorPrinting {
return false;
}

return !!this.cssDataManager.getPseudoElement("::" + match[1]);
return !!this.cssDataManager.getPseudoElement('::' + match[1]);
}

private selectorToSpecificityMarkedString(node: nodes.Node): MarkedString {
Expand Down Expand Up @@ -412,7 +405,7 @@ export class SelectorPrinting {

case nodes.NodeType.ElementNameSelector:
//ignore universal selector
if (element.matches("*")) {
if (element.matches('*')) {
break;
}

Expand All @@ -438,7 +431,7 @@ export class SelectorPrinting {
continue elementLoop;
}

specificity.tag++; // pseudo element
specificity.tag++; // pseudo element
continue elementLoop;
}

Expand Down Expand Up @@ -475,7 +468,7 @@ export class SelectorPrinting {
// https://www.w3.org/TR/selectors-4/#the-nth-child-pseudo
specificity.attr++;

// 23 = Binary Expression.
// 23 = Binary Expression.
if (childElements.length === 3 && childElements[1].type === 23) {
let mostSpecificListItem = calculateMostSpecificListItem(childElements[2].getChildren());

Expand All @@ -493,7 +486,7 @@ export class SelectorPrinting {
const firstToken = parser.scanner.scan();
const secondToken = parser.scanner.scan();

if (firstToken.text === 'n' || firstToken.text === '-n' && secondToken.text === 'of') {
if (firstToken.text === 'n' || (firstToken.text === '-n' && secondToken.text === 'of')) {
const complexSelectorListNodes: nodes.Node[] = [];
const complexSelectorText = pseudoSelectorText.slice(secondToken.offset + 2);
const complexSelectorArray = complexSelectorText.split(',');
Expand All @@ -516,7 +509,7 @@ export class SelectorPrinting {
continue elementLoop;
}

specificity.attr++; //pseudo class
specificity.attr++; //pseudo class
continue elementLoop;
}

Expand All @@ -532,13 +525,11 @@ export class SelectorPrinting {
};

const specificity = calculateScore(node);
return `[${l10n.t("Selector Specificity")}](https://developer.mozilla.org/docs/Web/CSS/Specificity): (${specificity.id}, ${specificity.attr}, ${specificity.tag})`;
return `[${l10n.t('Selector Specificity')}](https://developer.mozilla.org/docs/Web/CSS/Specificity): (${specificity.id}, ${specificity.attr}, ${specificity.tag})`;
}

}

class SelectorElementBuilder {

private prev: nodes.Node | null;
private element: Element;

Expand All @@ -564,7 +555,6 @@ class SelectorElementBuilder {
}

for (const selectorChild of selector.getChildren()) {

if (selectorChild instanceof nodes.SimpleSelector) {
if (this.prev instanceof nodes.SimpleSelector) {
const labelElement = new LabelElement('\u2026');
Expand All @@ -584,12 +574,13 @@ class SelectorElementBuilder {
this.element.addChild(root);
this.element = thisElement;
}
if (selectorChild instanceof nodes.SimpleSelector ||
if (
selectorChild instanceof nodes.SimpleSelector ||
selectorChild.type === nodes.NodeType.SelectorCombinatorParent ||
selectorChild.type === nodes.NodeType.SelectorCombinatorShadowPiercingDescendant ||
selectorChild.type === nodes.NodeType.SelectorCombinatorSibling ||
selectorChild.type === nodes.NodeType.SelectorCombinatorAllSiblings) {

selectorChild.type === nodes.NodeType.SelectorCombinatorAllSiblings
) {
this.prev = selectorChild;
}
}
Expand Down
Loading

0 comments on commit c17c287

Please sign in to comment.