Skip to content

Commit

Permalink
Add some autocomplete icons
Browse files Browse the repository at this point in the history
  • Loading branch information
FIameCaster committed Jul 29, 2024
1 parent 6c15a1d commit 5c34d25
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 22 deletions.
12 changes: 6 additions & 6 deletions package/scripts/buildCSSData.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -201,21 +201,21 @@ const lines = [
"",
'import { Completion } from "../types.js"',
"",
"const toCompletions = (prefix: string, values: string): Completion[] => {",
'\treturn values.split(",").map(val => ({ label: prefix + val }))',
"const toCompletions = (prefix: string, icon: string, values: string): Completion[] => {",
'\treturn values.split(",").map(val => ({ label: prefix + val, icon }))',
"}",
"",
]

let line = 'const cssValues = /* @__PURE__ */ toCompletions("", "'
let line = 'const cssValues = /* @__PURE__ */ toCompletions("", "keyword", "'

cssValues.forEach(val => {
line += val + ","
})

lines.push(line.slice(0, -1) + '")', "")

line = 'const atRules = /* @__PURE__ */ toCompletions("@", "'
line = 'const atRules = /* @__PURE__ */ toCompletions("@", "keyword", "'

data.atDirectives.forEach(({ name }) => {
if (name[1] != "-") line += name.slice(1) + ","
Expand All @@ -229,7 +229,7 @@ data.pseudoClasses.forEach(({ name }) => {
if (name[1] != "-") pseudos.add(name.slice(-2) == "()" ? name.slice(1, -2) : name.slice(1))
})

line = 'const pseudoClasses = /* @__PURE__ */ toCompletions(":", "'
line = 'const pseudoClasses = /* @__PURE__ */ toCompletions(":", "function", "'
pseudos.forEach(pseudo => {
line += pseudo + ","
})
Expand All @@ -242,7 +242,7 @@ data.pseudoElements.forEach(({ name }) => {
if (name[2] != "-") pseudos.add(name.slice(-2) == "()" ? name.slice(2, -2) : name.slice(2))
})

line = 'const pseudoElements = /* @__PURE__ */ toCompletions("::", "'
line = 'const pseudoElements = /* @__PURE__ */ toCompletions("::", "function", "'

pseudos.forEach(pseudo => {
line += pseudo + ","
Expand Down
59 changes: 59 additions & 0 deletions package/src/extensions/autocomplete/icons.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
.pce-ac-icon {
display: flex;
flex-shrink: 0;
align-items: center;
margin-right: -0.25em;
}

.pce-ac-icon::before {
content: "";
width: 16px;
height: 16px;
background: currentColor;
-webkit-mask: no-repeat var(--mask);
mask: no-repeat var(--mask);
}

.pce-ac-row[aria-selected] .pce-ac-icon::before {
color: var(--widget__color-active);
}

/* Icons from https://github.com/microsoft/vscode-codicons, but rewritten to 28% the size gzipped */

.pce-ac-icon-namespace {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path stroke="red" fill="none" d="m6 2.5 a2 2 0 0 0-2 2v1.5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v1.5a2 2 0 0 0 2 2m4-11a2 2 0 0 1 2 2v1.5a2 2 0 0 0 2 2 2 2 0 0 0-2 2v1.5a2 2 0 0 1-2 2"/></svg>');
}
.pce-ac-icon-unit {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path stroke="red" fill="none" stroke-linejoin="bevel" d="m3.5 1.5h9v13h-9zm.5 2h4m-4 3h2m-2 3h4m-4 3h2"/></svg>');
}
.pce-ac-icon-parameter {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" stroke="red" fill="none"><path stroke-linejoin="bevel" d="m3.3 6.2l-1.8 1.8 1.8 1.8m9.4 0l1.8-1.8-1.8-1.8"/><path d="m5.5 6v-1.5h5v1.5m-2.5-1v6.5h1.5-3m.5 0a1 1 0 0 0 1-1 1 1 0 0 0 1 1m1.5-6a1 1 0 0 0-1-1m-3 0a1 1 0 0 0-1 1"/></svg>');
}
.pce-ac-icon-snippet {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path stroke="red" fill="none" stroke-linejoin="bevel" d="m2.5 13v-11.5h12v11.5m-12.5 1.5h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1m1 0h1"/></svg>');
}
.pce-ac-icon-value,
.pce-ac-icon-enum {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m2 7h6l1 1v5l-1 1h-6l-1-1v-5zv6h6v-5h-6zm1 2h4v1h-4zm0 2h4v1h-4zm4-5v-3l1-1h6l1 1v5l-1 1h-4v-1h4v-5h-6v3zm2-2h4v1h-4zm0 2h4v1h-3.59l-.41-.41z"/></svg>');
}
.pce-ac-icon-interface {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path stroke="red" fill="none" d="m4 8h4.5a3 3 0 0 1 6 0 3 3 0 0 1-6 0"/><circle cx="3" cy="8" r="2"/></svg>');
}
.pce-ac-icon-constant {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path stroke="red" fill="none" stroke-linejoin="bevel" d="m1.5 3.5h13v9h-13zm2.5 3h8m0 3h-8"/></svg>');
}
.pce-ac-icon-class {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path stroke="red" fill="none" stroke-linejoin="bevel" d="m5.5 1.5 l2 2-4 4-2-2 4-4zm0 4v7h5.5m-.5.5l2.5-2.5 1.5 1.5-2.5 2.5zm-5.5-5.5h6m-.5.5l2.5-2.5 1.5 1.5-2.5 2.5z"/></svg>');
}
.pce-ac-icon-property {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path stroke="red" fill="none" d="m7.24 6.86a3.8 3.8 0 0 1 5.02-5.02l-2.8 2.8 1.9 1.9 2.8-2.8a3.8 3.8 0 0 1-5.02 5.02l-5.35 5.35a.95.95 0 0 1-1.9-1.9z"/></svg>');
}
.pce-ac-icon-variable {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path stroke="red" fill="none" stroke-linejoin="bevel" d="m4 4.5h-2.5v8h2.5m8 0h2.5v-8h-2.5m-3 1l2.5 1.5v2.5l-4.5 2-2.5-1.5v-2.5zm-4.5 2l2.5 1.5v2.5m0-2.5l4.5-2"/></svg>');
}
.pce-ac-icon-function {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path stroke="red" fill="none" stroke-linejoin="bevel" d="m8 1.25l5.5 3.3v6.6l-5.5 3.3-5.5-3.3v-6.6zM2.5 4.85l5.5 3v6.5m0-6.5l5.5-3"/></svg>');
}
.pce-ac-icon-keyword {
--mask: url('data:image/svg+xml,<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path stroke="red" fill="none" d="m1.5 2.5h6v2h-6zm8.5 1h5m-14 4h11m-11 3h6m3 0h5m-3 3h2m-13 0h9"/></svg>');
}
5 changes: 3 additions & 2 deletions package/src/extensions/autocomplete/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@

.pce-ac-row {
display: flex;
padding: 0 0.3em;
padding: 0 0.2em;
height: 1.4em;
align-items: center;
justify-content: space-between;
gap: 0.5em;
cursor: pointer;
}
Expand All @@ -43,6 +42,7 @@
.pce-ac-details {
opacity: 0.8;
font-size: 90%;
margin-left: auto;
}

.pce-ac-details:empty {
Expand All @@ -57,4 +57,5 @@

.pce-ac-row[aria-selected] {
background: #1094ff36;
color: var(--widget__color-active);
}
18 changes: 13 additions & 5 deletions package/src/extensions/autocomplete/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ let count = 0

const template = createTemplate("<div class=pce-ac-tooltip><ul role=listbox>")
const rowTemplate = createTemplate(
"<li class=pce-ac-row role=option><div> <span> </span> </div><div class=pce-ac-details> ",
"<li class=pce-ac-row role=option><div></div><div> <span> </span> </div><div class=pce-ac-details> ",
)
const matchTemplate = createTemplate("<span> ")

Expand All @@ -36,7 +36,7 @@ const registerCompletions = <T extends object>(
}

/**
* Extension adding basic auto-complete to an editor. For auto-completion to work, you need to
* Extension adding basic autocomplete to an editor. For autocompletion to work, you need to
* {@link registerCompletions} for specific languages.
*
* @param config Object used to configure the extension. The `filter` property is required.
Expand Down Expand Up @@ -64,6 +64,7 @@ const autoComplete =
const id = (list.id = "pce-ac-" + count++)
const rows = list.children as HTMLCollectionOf<HTMLLIElement>
const add = editor.addListener
const prevIcons: string[] = []
const hide = () => {
if (isOpen) {
_hide()
Expand All @@ -83,8 +84,11 @@ const autoComplete =

const updateRow = (index: number) => {
const option = currentOptions[index + offset]
const nodes = rows[index].firstChild!.childNodes
const label = option[3].label
const [iconEl, labelEl, detailsEl] = rows[index].children as HTMLCollectionOf<HTMLDivElement>
const nodes = labelEl.childNodes
const completion = option[3]
const label = completion.label
const icon = completion.icon || "variable"
const matched = option[1]

let nodeCount = nodes.length - 1
Expand All @@ -103,7 +107,11 @@ const autoComplete =
nodes[--nodeCount].remove()
}
updateNode(nodes[l] as Text, label.slice(pos))
updateNode(rows[index].lastChild!.firstChild as Text, option[3].detail || "")
updateNode(detailsEl.firstChild as Text, completion.detail || "")
if (prevIcons[index] != icon) {
iconEl.className = `pce-ac-icon pce-ac-icon-${(prevIcons[index] = icon)}`
iconEl.style.color = `var(--pce-ac-icon-${icon})`
}
}

const scrollActiveIntoView = () => {
Expand Down
46 changes: 38 additions & 8 deletions package/src/extensions/autocomplete/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,34 @@ export interface Completion {
boost?: number
/** Optional, short piece of information displayed after the label. */
detail?: string
/**
* Name of the icon shown before the label. This name is appended to the class
* `pce-ac-icon-`, so i.e. `.pce-ac-icon-variable` can be used to style icons with the
* name `variable`.
*
* The icon element also gets it color set to the CSS variable `--pce-ac-icon-` followed
* by the icon name. Use these CSS variables to set different colors for different icons.
*
* `prism-code-editor/autocomplete-icons.css` adds 13 icons: `class`, `constant`, `enum`,
* `function`, `interface`, `keyword`, `namespace`, `parameter`, `property`, `snippet`,
* `unit`, and `variable`. You can define your own icons instead.
*
* Defaults to `"variable"`
*/
icon?:
| "class"
| "constant"
| "enum"
| "function"
| "interface"
| "keyword"
| "namespace"
| "parameter"
| "property"
| "snippet"
| "unit"
| "variable"
| (string & {})
}

export interface CompletionResult {
Expand Down Expand Up @@ -39,18 +67,20 @@ export interface CompletionContext {

/**
* Completion definition for a language.
*
*
* The context property can be used to add extra properties to the context
* passed to the completion sources. This is useful to do certain computations once
* instead of once for each source.
*/
export type CompletionDefinition<T extends object> = {
context?: null,
sources: CompletionSource[]
} | {
context(context: CompletionContext, editor: PrismEditor): T
sources: CompletionSource<T>[]
}
export type CompletionDefinition<T extends object> =
| {
context?: null
sources: CompletionSource[]
}
| {
context(context: CompletionContext, editor: PrismEditor): T
sources: CompletionSource<T>[]
}

export type AutoCompleteConfig = {
/** Function used to filter and rank options. */
Expand Down
3 changes: 2 additions & 1 deletion package/src/testsite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "../prism/languages/regex"
import "../extensions/copyButton/copy.css"
import "../extensions/folding/folding.css"
import "../extensions/autocomplete/style.css"
import "../extensions/autocomplete/icons.css"
import { cursorPosition } from "../extensions/cursor"
import { indentGuides } from "../extensions/guides"
import guides from "../prism/core?raw"
Expand Down Expand Up @@ -59,7 +60,7 @@ const runBtn = <HTMLButtonElement>document.getElementById("run"),
editHistory(),
autoComplete({
filter: fuzzyFilter,
// closeOnBlur: false,
closeOnBlur: false,
}),
),
startCode = `<!DOCTYPE html>
Expand Down

0 comments on commit 5c34d25

Please sign in to comment.