Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support hover tooltip for scss #367

Merged
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
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
Loading