Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[pre-commit.ci lite] apply automatic fixes
Browse files Browse the repository at this point in the history
pre-commit-ci-lite[bot] authored Mar 25, 2024
1 parent 3467420 commit 8baf3e5
Showing 9 changed files with 291 additions and 248 deletions.
14 changes: 7 additions & 7 deletions packages/generate-examples/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/* eslint-disable */
export default {
displayName: 'generate-examples',
preset: '../../jest.preset.js',
displayName: "generate-examples",
preset: "../../jest.preset.js",
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
"ts-jest": {
tsconfig: "<rootDir>/tsconfig.spec.json",
},
},
transform: {
'^.+\\.[tj]s$': 'ts-jest',
"^.+\\.[tj]s$": "ts-jest",
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/libs/generate-examples',
moduleFileExtensions: ["ts", "js", "html"],
coverageDirectory: "../../coverage/libs/generate-examples",
};
2 changes: 1 addition & 1 deletion packages/generate-examples/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export * from './lib/generate-examples';
export * from "./lib/generate-examples";
21 changes: 11 additions & 10 deletions packages/generate-examples/src/lib/buildDictionary.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { loadFixture } from './loadFixture.js';
import { loadFixture } from "./loadFixture.js";

Promise.all([
loadFixture('actions', 'bringArgMadeAfterLook'),
loadFixture('decorations', 'chuckBlockAirUntilBatt'),
loadFixture('decorations', 'cutFine'),
loadFixture('decorations', 'chuckLineFine'),
loadFixture('actions', 'bringAirAndBatAndCapToAfterItemEach'),
loadFixture("actions", "bringArgMadeAfterLook"),
loadFixture("decorations", "chuckBlockAirUntilBatt"),
loadFixture("decorations", "cutFine"),
loadFixture("decorations", "chuckLineFine"),
loadFixture("actions", "bringAirAndBatAndCapToAfterItemEach"),
]).then((allItems) => {
allItems.forEach((item) => {
if (item)
if (item) {
console.log(`
.wrapper
.before
${item.before.replace(/\n/gi, '\n ')}
${item.before.replace(/\n/gi, "\n ")}
.during
${(item.during || item.before).replace(/\n/gi, '\n ')}
${(item.during || item.before).replace(/\n/gi, "\n ")}
.command ${item.command}
.after
${item.after.replace(/\n/gi, '\n ')}
${item.after.replace(/\n/gi, "\n ")}
`);
}
});
});
133 changes: 75 additions & 58 deletions packages/generate-examples/src/lib/buildSpokenForm.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import path from 'path';
import fs from 'fs-extra';
const cursorlessRoot = path.resolve('../../../src');
import path from "path";
import fs from "fs-extra";
const cursorlessRoot = path.resolve("../../../src");

const commandDictionaryJson = fs.readJSONSync(
path.join(
cursorlessRoot,
'../cursorless-nx/libs/cheatsheet/src/lib/data/sampleSpokenFormInfos/defaults.json'
)
) as typeof import('../../../cheatsheet/src/lib/data/sampleSpokenFormInfos/defaults.json');
"../cursorless-nx/libs/cheatsheet/src/lib/data/sampleSpokenFormInfos/defaults.json",
),
) as typeof import("../../../cheatsheet/src/lib/data/sampleSpokenFormInfos/defaults.json");

const letters = 'abcdefghijklmnopqrstuvwxyz'.split('');
const letters = "abcdefghijklmnopqrstuvwxyz".split("");
const defaultAlphabet =
'air bat cap drum each fine gust harp sit jury crunch look made near odd pit quench red sun trap urge vest whale plex yank zip'
.split(' ')
"air bat cap drum each fine gust harp sit jury crunch look made near odd pit quench red sun trap urge vest whale plex yank zip"
.split(" ")
.map((word, index) => [letters[index], word] as const);

const defaultDigits = 'zero one two three four five six seven eight nine'
.split(' ')
const defaultDigits = "zero one two three four five six seven eight nine"
.split(" ")
.map((word, index) => [`${index}`, word] as const);

const letterDictionary = defaultAlphabet
@@ -32,48 +32,48 @@ const commandDictionary = commandDictionaryJson.sections.reduce(
...r,
[id]: variations[0].spokenForm,
}),
{}
{},
),
};
},
{} as Record<
typeof commandDictionaryJson['sections'][number]['id'],
(typeof commandDictionaryJson)["sections"][number]["id"],
Record<
typeof commandDictionaryJson['sections'][number]['items'][number]['id'],
typeof commandDictionaryJson['sections'][number]['items'][number]['variations'][0]['spokenForm']
(typeof commandDictionaryJson)["sections"][number]["items"][number]["id"],
(typeof commandDictionaryJson)["sections"][number]["items"][number]["variations"][0]["spokenForm"]
>
>
>,
);

type CommandDictionary = typeof commandDictionary;

interface Modifier {
type: 'position' | 'containingScope';
position: keyof CommandDictionary['positions'];
type: "position" | "containingScope";
position: keyof CommandDictionary["positions"];
scopeType: {
type: keyof CommandDictionary['scopes'];
type: keyof CommandDictionary["scopes"];
};
}
type PrimitiveTarget = {
type: 'primitive';
type: "primitive";
modifiers?: Modifier[];
position?: keyof CommandDictionary['positions'];
position?: keyof CommandDictionary["positions"];
mark?: {
type: 'decoratedSymbol';
type: "decoratedSymbol";
character: keyof typeof letterDictionary;
};
};

type RangeTarget = {
type: 'range';
type: "range";
excludeAnchor?: boolean;
excludeActive?: boolean;
anchor: Target;
active: Target;
};

type ListTarget = {
type: 'list';
type: "list";
elements: Target[];
};

@@ -82,7 +82,7 @@ type Command = { action: { name: string }; targets: Target[] };

function interpolate(
template: string,
handleAction: (counter: number) => string
handleAction: (counter: number) => string,
) {
let counter = -1;
return template.replace(/<[a-z0-9]+>/gi, () => handleAction(++counter));
@@ -97,22 +97,24 @@ class SpokenForm {

public build() {
return interpolate(this.getTemplate(), (counter) =>
this.parseTarget(this.command.targets[counter])
this.parseTarget(this.command.targets[counter]),
);
}

private getTemplate() {
return commandDictionary['actions'][this.command.action.name];
return commandDictionary["actions"][this.command.action.name];
}

private parseTarget(target: Target): string {
if (!target) throw new Error(`Excess mark`);
if (!target) {
throw new Error(`Excess mark`);
}
switch (target.type) {
case 'primitive':
case "primitive":
return this.parsePrimitiveTarget(target);
case 'range':
case "range":
return this.parseRangeTarget(target);
case 'list':
case "list":
return this.parseListTarget(target);
default: {
// @ts-expect-error - if this is hit we need to add new cases and types
@@ -126,58 +128,73 @@ class SpokenForm {
?.filter((v): v is Modifier => !!v)
?.map((mod) => {
switch (mod.type) {
case 'position':
return commandDictionary['positions'][mod.position];
case 'containingScope':
return commandDictionary['scopes'][mod.scopeType.type];
case "position":
return commandDictionary["positions"][mod.position];
case "containingScope":
return commandDictionary["scopes"][mod.scopeType.type];
}
throw new Error(`Unknown modifier type ${mod.type}`);
})
.join(' ') || '';
.join(" ") || "";
if (target.position) {
prefix = `${commandDictionary['positions'][target.position]} ${prefix}`;
prefix = `${commandDictionary["positions"][target.position]} ${prefix}`;
}
if (target.mark) {
if (target.mark.type !== 'decoratedSymbol')
if (target.mark.type !== "decoratedSymbol") {
throw new Error(`Unknown target type ${target.mark.type}`);
}
return (
(prefix ? prefix + ' ' : '') + letterDictionary[target.mark.character]
(prefix ? prefix + " " : "") + letterDictionary[target.mark.character]
);
}
if (!prefix) throw new Error(`Unknown mark`);
if (!prefix) {
throw new Error(`Unknown mark`);
}
return prefix;
}

parseRangeTarget(target: RangeTarget) {
let compoundTargetKey;
if (target.excludeAnchor && target.excludeActive)
compoundTargetKey = 'rangeExclusive';
else if (!target.excludeAnchor && !target.excludeActive)
compoundTargetKey = 'rangeInclusive';
else if (!target.excludeAnchor && target.excludeActive)
compoundTargetKey = 'rangeExcludingEnd';
else throw new Error(`Bad inclusion range`);
if (target.excludeAnchor && target.excludeActive) {
compoundTargetKey = "rangeExclusive";
} else if (!target.excludeAnchor && !target.excludeActive) {
compoundTargetKey = "rangeInclusive";
} else if (!target.excludeAnchor && target.excludeActive) {
compoundTargetKey = "rangeExcludingEnd";
} else {
throw new Error(`Bad inclusion range`);
}
return interpolate(
commandDictionary['compoundTargets'][compoundTargetKey],
commandDictionary["compoundTargets"][compoundTargetKey],
(index) => {
if (index === 0) return this.parseTarget(target.anchor);
if (index === 1) return this.parseTarget(target.active);
return '';
}
if (index === 0) {
return this.parseTarget(target.anchor);
}
if (index === 1) {
return this.parseTarget(target.active);
}
return "";
},
);
}
parseListTarget(target: ListTarget) {
return target.elements.reduce((result, element) => {
if (!result) return this.parseTarget(element);
if (!result) {
return this.parseTarget(element);
}
return interpolate(
commandDictionary['compoundTargets']['listConnective'],
commandDictionary["compoundTargets"]["listConnective"],
(index) => {
if (index === 0) return result;
if (index === 1) return this.parseTarget(element);
if (index === 0) {
return result;
}
if (index === 1) {
return this.parseTarget(element);
}
throw Error(`Invalid List`);
}
},
);
}, '');
}, "");
}
}

2 changes: 1 addition & 1 deletion packages/generate-examples/src/lib/generate-examples.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export function generateExamples(): string {
return 'generate-examples';
return "generate-examples";
}
70 changes: 35 additions & 35 deletions packages/generate-examples/src/lib/generateHtml.spec.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import prettier from 'prettier';
import { generateHtml as unformettedFunc } from './generateHtml';
import prettier from "prettier";
import { generateHtml as unformettedFunc } from "./generateHtml";

async function generateHtml(...args: Parameters<typeof unformettedFunc>) {
return prettier.format(await unformettedFunc(...args), {
singleAttributePerLine: true,
htmlWhitespaceSensitivity: 'ignore',
parser: 'babel',
htmlWhitespaceSensitivity: "ignore",
parser: "babel",
});
}

describe('generateHtml', () => {
it('should select whole line', async () => {
describe("generateHtml", () => {
it("should select whole line", async () => {
expect(
await generateHtml(
{
documentContents: ' const oneLine = 1;\nconst line2 = 2;',
documentContents: " const oneLine = 1;\nconst line2 = 2;",
selections: [
{
type: 'line',
type: "line",
anchor: { line: 1, character: 0 },
active: { line: 1, character: 22 },
},
],
},

'typescript'
)
"typescript",
),
).toMatchInlineSnapshot(`
"<pre
class=\\"shiki\\"
@@ -58,22 +58,22 @@ describe('generateHtml', () => {
"
`);
});
it('should select single token', async () => {
it("should select single token", async () => {
expect(
await generateHtml(
{
documentContents: ' const oneLine = 1;\nconst line2 = 2;',
documentContents: " const oneLine = 1;\nconst line2 = 2;",
selections: [
{
type: 'selection',
type: "selection",
anchor: { line: 0, character: 8 },
active: { line: 0, character: 15 },
},
],
},

'typescript'
)
"typescript",
),
).toMatchInlineSnapshot(`
"<pre
class=\\"shiki\\"
@@ -112,22 +112,22 @@ describe('generateHtml', () => {
`);
});

it('should select multiple tokens', async () => {
it("should select multiple tokens", async () => {
expect(
await generateHtml(
{
documentContents: 'const oneLine = 1;',
documentContents: "const oneLine = 1;",
selections: [
{
type: 'selection',
type: "selection",
anchor: { line: 0, character: 6 },
active: { line: 0, character: 17 },
},
],
},

'typescript'
)
"typescript",
),
).toMatchInlineSnapshot(`
"<pre
class=\\"shiki\\"
@@ -155,22 +155,22 @@ describe('generateHtml', () => {
`);
});

it('should select inside tokens', async () => {
it("should select inside tokens", async () => {
expect(
await generateHtml(
{
documentContents: 'const oneLine = "line";',
selections: [
{
type: 'selection',
type: "selection",
anchor: { line: 0, character: 9 },
active: { line: 0, character: 19 },
},
],
},

'typescript'
)
"typescript",
),
).toMatchInlineSnapshot(`
"<pre
class=\\"shiki\\"
@@ -202,22 +202,22 @@ describe('generateHtml', () => {
`);
});

it('should select inside single token', async () => {
it("should select inside single token", async () => {
expect(
await generateHtml(
{
documentContents: 'const oneLine = 1;',
documentContents: "const oneLine = 1;",
selections: [
{
type: 'selection',
type: "selection",
anchor: { line: 0, character: 9 },
active: { line: 0, character: 11 },
},
],
},

'typescript'
)
"typescript",
),
).toMatchInlineSnapshot(`
"<pre
class=\\"shiki\\"
@@ -246,29 +246,29 @@ describe('generateHtml', () => {
"
`);
});
it('should select superset ranges', async () => {
it("should select superset ranges", async () => {
expect(
await generateHtml(
{
documentContents: 'const oneLine = 1;',
documentContents: "const oneLine = 1;",
selections: [
{
type: 'selection',
type: "selection",
anchor: { line: 0, character: 9 },
active: { line: 0, character: 11 },
},
],
thatMark: [
{
type: 'selection',
type: "selection",
anchor: { line: 0, character: 6 },
active: { line: 0, character: 13 },
},
],
},

'typescript'
)
"typescript",
),
).toMatchInlineSnapshot(`
"<pre
class=\\"shiki\\"
153 changes: 88 additions & 65 deletions packages/generate-examples/src/lib/generateHtml.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { getHighlighter, Lang } from 'shiki';
import { renderToHtml, HatType, SelectionType, Token } from './renderToHtml';
import { getHighlighter, Lang } from "shiki";
import { renderToHtml, HatType, SelectionType, Token } from "./renderToHtml";

export interface SelectionAnchor {
line: number;
character: number;
}

interface CursorlessFixtureSelection {
type: 'line' | 'selection';
type: "line" | "selection";
name?: string;
anchor: SelectionAnchor;
active: SelectionAnchor;
@@ -28,7 +28,7 @@ export async function generateHtml(state: CursorlessFixtureState, lang: Lang) {
return new HTMLGenerator(state, lang).generate();
}

const highlighter = getHighlighter({ theme: 'css-variables' });
const highlighter = getHighlighter({ theme: "css-variables" });

class HTMLGenerator {
private state: CursorlessFixtureState;
@@ -48,8 +48,8 @@ class HTMLGenerator {
this.applyMarks();
this.applyAllSelections();
return renderToHtml(this.tokens, {
bg: 'var(--shiki-color-background)',
fg: 'var(--shiki-color-text)',
bg: "var(--shiki-color-background)",
fg: "var(--shiki-color-text)",
lineOptions: this.lineOptions,
});
}
@@ -62,31 +62,33 @@ class HTMLGenerator {
(token) =>
({
...token,
type: 'token',
} as Token)
)
type: "token",
}) as Token,
),
);
}

applyMarks() {
Object.entries(this.state.marks || {}).forEach(([key, mark]) => {
const [type, letterArg] = key.split('.') as [HatType, string];
const letter = !letterArg || letterArg === '' ? '.' : letterArg;
const [type, letterArg] = key.split(".") as [HatType, string];
const letter = !letterArg || letterArg === "" ? "." : letterArg;
const line = this.tokens[mark.start.line];
if (!line) return;
if (!line) {
return;
}
this.insertHat(
line as Extract<Token, { type: 'token' | 'hat' }>[],
line as Extract<Token, { type: "token" | "hat" }>[],
type,
letter,
mark.start.character
mark.start.character,
);
});
}
insertHat(
line: Extract<Token, { type: 'token' | 'hat' }>[],
line: Extract<Token, { type: "token" | "hat" }>[],
hatType: HatType,
markCharacter: string,
wordStart: number
wordStart: number,
) {
let rawIndex = 0;
for (let t = 0; t < line.length; t += 1) {
@@ -103,11 +105,11 @@ class HTMLGenerator {
1,
{ ...token, content: token.content.substring(0, i) },
{
type: 'hat',
type: "hat",
hatType,
content: token.content.substring(i, i + 1),
},
{ ...token, content: token.content.substring(i + 1) }
{ ...token, content: token.content.substring(i + 1) },
);
return;
}
@@ -117,24 +119,26 @@ class HTMLGenerator {
}

applyAllSelections() {
if (!this.applySelectionsFromState('decorations')) {
this.applySelectionsFromState('selections');
if (!this.applySelectionsFromState("decorations")) {
this.applySelectionsFromState("selections");
}
this.applySelectionsFromState('thatMark');
this.applySelectionsFromState('sourceMark');
this.applySelectionsFromState("thatMark");
this.applySelectionsFromState("sourceMark");
}

applySelectionsFromState(
key: 'decorations' | 'selections' | 'thatMark' | 'sourceMark'
key: "decorations" | "selections" | "thatMark" | "sourceMark",
): boolean {
const selections = this.state[key];
if (!selections?.length) return false;
if (!selections?.length) {
return false;
}
const selectionParser = new SelectionParser(
this.tokens,
key.replace(/s$/gi, '') as SelectionType
key.replace(/s$/gi, "") as SelectionType,
);
selections.forEach((selection) => {
if (selection.type === 'line') {
if (selection.type === "line") {
return this.applyLineSelection(key, selection);
}
selectionParser.parse(selection);
@@ -144,9 +148,9 @@ class HTMLGenerator {

getSelectionClasses(
selectionType: keyof typeof this.state,
selection: CursorlessFixtureSelection
selection: CursorlessFixtureSelection,
) {
const classes = [selectionType.replace(/s$/g, '')];
const classes = [selectionType.replace(/s$/g, "")];
if (selection.name) {
classes.push(selection.name);
}
@@ -155,15 +159,16 @@ class HTMLGenerator {

applyLineSelection(
selectionType: keyof typeof this.state,
selection: CursorlessFixtureSelection
selection: CursorlessFixtureSelection,
) {
const classes = this.getSelectionClasses(selectionType, selection);
const { anchor: start, active: end } = selection;
for (let i = start.line + 1; i <= end.line + 1; i += 1)
for (let i = start.line + 1; i <= end.line + 1; i += 1) {
this.lineOptions.push({
line: i,
classes,
});
}
}
}

@@ -178,7 +183,7 @@ class SelectionParser {

parse(selection: CursorlessFixtureSelection) {
let start, end;
if (selection.type === 'UntypedTarget') {
if (selection.type === "UntypedTarget") {
start = selection.contentRange.start.line;
end = selection.contentRange.end.line;
} else {
@@ -197,27 +202,30 @@ class SelectionParser {
parseLine(l: number, start: SelectionAnchor, end: SelectionAnchor) {
const lineParser = new SelectionLineParser(
this.selectionType,
this.lines[l]
this.lines[l],
);
if (end.line === start.line)
if (end.line === start.line) {
return lineParser.parse(start.character, end.character);
if (l === end.line) return lineParser.parse(0, end.character);
}
if (l === end.line) {
return lineParser.parse(0, end.character);
}
return lineParser.parse(start.character, Infinity);
}

handleInsideLine(currentLine: number) {
this.lines[currentLine] = [
{
type: 'selection',
type: "selection",
selection: this.lines[currentLine],
className: this.selectionType,
},
];
}
}

type BaseToken = Exclude<Token, { type: 'selection' }>;
type SelectionToken = Extract<Token, { type: 'selection' }>;
type BaseToken = Exclude<Token, { type: "selection" }>;
type SelectionToken = Extract<Token, { type: "selection" }>;

class SelectionLineParser {
selectionType: SelectionType;
@@ -242,20 +250,27 @@ class SelectionLineParser {
}

getTokenState(tokenStart: number, tokenEnd: number) {
if (tokenEnd <= this.startIndex || this.endIndex <= tokenStart)
return 'outside';
if (tokenStart === this.startIndex && tokenEnd === this.endIndex)
return 'entire';
if (!this.getCurrentSelectionToken() && tokenEnd >= this.endIndex)
return 'inner';
if (!this.getCurrentSelectionToken()) return 'start';
if (tokenEnd >= this.endIndex) return 'end';
return 'continue';
if (tokenEnd <= this.startIndex || this.endIndex <= tokenStart) {
return "outside";
}
if (tokenStart === this.startIndex && tokenEnd === this.endIndex) {
return "entire";
}
if (!this.getCurrentSelectionToken() && tokenEnd >= this.endIndex) {
return "inner";
}
if (!this.getCurrentSelectionToken()) {
return "start";
}
if (tokenEnd >= this.endIndex) {
return "end";
}
return "continue";
}

getCurrentSelectionToken() {
const lastResult = this.result[this.result.length - 1];
return lastResult?.type === 'selection' ? lastResult : undefined;
return lastResult?.type === "selection" ? lastResult : undefined;
}

parse(startIndex: number, endIndex: number) {
@@ -269,34 +284,38 @@ class SelectionLineParser {
}

parseToken(token: Token | undefined) {
if (!token) return;
if (token.type === 'selection') return this.parseSelection(token);
if (!token) {
return;
}
if (token.type === "selection") {
return this.parseSelection(token);
}
const tokenStart = this.rawIndex;
this.incrementRawIndex(token);
const tokenEnd = this.rawIndex;
const state = this.getTokenState(tokenStart, tokenEnd);
switch (state) {
case 'outside': {
case "outside": {
this.result.push(token);
return;
}
case 'entire': {
case "entire": {
this.createSelection(token);
return;
}
case 'start': {
case "start": {
this.startSelection(token);
return;
}
case 'continue': {
case "continue": {
this.getCurrentSelectionToken()?.selection.push(token);
return;
}
case 'end': {
case "end": {
this.endSelection(token);
return;
}
case 'inner': {
case "inner": {
this.innerSelection(token);
return;
}
@@ -306,17 +325,17 @@ class SelectionLineParser {
parseSelection(token: SelectionToken) {
this.activeSelectionTypes.push(token.className);
this.result.push({
type: 'selection',
className: this.activeSelectionTypes.join(' '),
type: "selection",
className: this.activeSelectionTypes.join(" "),
selection: [],
});
for (const subToken of token.selection) {
this.parseToken(subToken);
}
this.activeSelectionTypes.pop();
this.result.push({
type: 'selection',
className: this.activeSelectionTypes.join(' '),
type: "selection",
className: this.activeSelectionTypes.join(" "),
selection: [],
});
}
@@ -331,11 +350,12 @@ class SelectionLineParser {

createSelection(token: Token) {
this.activeSelectionTypes.push(
(token as SelectionToken).className || this.getCurrentSelectionClassName()
(token as SelectionToken).className ||
this.getCurrentSelectionClassName(),
);
this.result.push({
type: 'selection',
className: this.activeSelectionTypes.join(' '),
type: "selection",
className: this.activeSelectionTypes.join(" "),
selection: [token],
});
}
@@ -364,11 +384,12 @@ class SelectionLineParser {
content: selectionContent,
});
this.activeSelectionTypes.pop();
if (postSelectionContent.length)
if (postSelectionContent.length) {
this.result.push({
...token,
content: postSelectionContent,
});
}
}

innerSelection(token: BaseToken) {
@@ -378,19 +399,21 @@ class SelectionLineParser {
const preSelectionContent = token.content.substring(0, stringStart);
const selectionContent = token.content.substring(stringStart, stringEnd);
const postSelectionContent = token.content.substring(stringEnd);
if (preSelectionContent.length)
if (preSelectionContent.length) {
this.result.push({
...token,
content: preSelectionContent,
});
}
this.createSelection({
...token,
content: selectionContent,
});
if (postSelectionContent.length)
if (postSelectionContent.length) {
this.result.push({
...token,
content: postSelectionContent,
});
}
}
}
44 changes: 23 additions & 21 deletions packages/generate-examples/src/lib/loadFixture.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import path from 'path';
import fs from 'fs-extra';
import * as yaml from 'yaml';
import path from "path";
import fs from "fs-extra";
import * as yaml from "yaml";

import { generateHtml, SelectionAnchor } from './generateHtml';
import { generateHtml, SelectionAnchor } from "./generateHtml";

const fixturesDir = path.join(
'../',
'cursorless-vscode-e2e',
'src',
'suite',
'fixtures',
'recorded'
"../",
"cursorless-vscode-e2e",
"src",
"suite",
"fixtures",
"recorded",
);

async function safeGenerateHtml(
@@ -20,20 +20,22 @@ async function safeGenerateHtml(
try {
return await generateHtml(state, languageId);
} catch (e) {
console.log('error in state', stateName, e);
console.log("error in state", stateName, e);
console.log(JSON.stringify(state, null, 2));
throw e;
}
}

export async function loadFixture(folder: string, name: string) {
const filepath = path.join(fixturesDir, folder, `${name}.yml`);
const data = yaml.parse(await fs.readFile(filepath, 'utf-8'));
if (data.command.version !== 2) return;
const data = yaml.parse(await fs.readFile(filepath, "utf-8"));
if (data.command.version !== 2) {
return;
}
try {
const during = data.decorations
? await safeGenerateHtml(
'decorations',
"decorations",
{
...data.initialState,
decorations: data.decorations.map(
@@ -52,21 +54,21 @@ export async function loadFixture(folder: string, name: string) {
type,
anchor: start,
active: end,
})
}),
),
},
data.languageId
data.languageId,
)
: undefined;
const before = await safeGenerateHtml(
'initialState',
"initialState",
data.initialState,
data.languageId
data.languageId,
);
const after = await safeGenerateHtml(
'finalState',
"finalState",
data.finalState,
data.languageId
data.languageId,
);
return {
language: data.languageId,
@@ -77,7 +79,7 @@ export async function loadFixture(folder: string, name: string) {
after,
};
} catch (e) {
console.log('error', filepath, e);
console.log("error", filepath, e);
console.log(JSON.stringify(data, null, 2));
throw e;
}
100 changes: 50 additions & 50 deletions packages/generate-examples/src/lib/renderToHtml.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// forked from https://github.com/SimeonC/shiki/blob/main/packages/shiki/src/renderer.ts

import { IThemedToken } from 'shiki';
import { IThemedToken } from "shiki";

// MIT License
const FontStyle = {
@@ -13,7 +13,7 @@ const FontStyle = {

export function groupBy<TObject>(
elements: TObject[],
keyGetter: (element: TObject) => string
keyGetter: (element: TObject) => string,
) {
const map = new Map();
for (const element of elements) {
@@ -28,31 +28,31 @@ export function groupBy<TObject>(
return map;
}

export type HatType = 'default';
export type HatType = "default";
interface BaseElementProps {
style?: string;
children: string;
className?: string;
}

const elements = {
pre({ className, style = '', children }: BaseElementProps) {
pre({ className, style = "", children }: BaseElementProps) {
return `<pre class="${className}" style="${style}">${children}</pre>`;
},

code({ children }: Pick<BaseElementProps, 'children'>) {
code({ children }: Pick<BaseElementProps, "children">) {
return `<code>${children}</code>`;
},

line({ className, children }: Omit<BaseElementProps, 'style'>) {
line({ className, children }: Omit<BaseElementProps, "style">) {
return `<span class="${className}">${children}</span>`;
},

token({ style = '', children }: Omit<BaseElementProps, 'className'>) {
token({ style = "", children }: Omit<BaseElementProps, "className">) {
return `<span style="${style}">${children}</span>`;
},

selection({ style = '', className, children }: BaseElementProps) {
selection({ style = "", className, children }: BaseElementProps) {
return `<span className="${className}" style="${style}">${children}</span>`;
},

@@ -61,21 +61,21 @@ const elements = {
},
} as const;
export type SelectionType =
| 'decoration'
| 'selection'
| 'thatMark'
| 'sourceMark';
| "decoration"
| "selection"
| "thatMark"
| "sourceMark";
export type Token =
| ({
type: 'token';
type: "token";
} & IThemedToken)
| {
type: 'selection';
type: "selection";
className: string;
selection: Token[];
}
| {
type: 'hat';
type: "hat";
hatType: HatType;
content: string;
};
@@ -87,103 +87,103 @@ export function renderToHtml(
bg?: string;
fg?: string;
lineOptions?: { line: string }[];
} = {}
} = {},
) {
const bg = options.bg || '#fff';
const bg = options.bg || "#fff";
const optionsByLineNumber = groupBy(
options.lineOptions ?? [],
(option) => option.line
(option) => option.line,
);

function h<
TType extends keyof typeof elements,
TProps extends BaseElementProps = Parameters<typeof elements[TType]>[0]
>(type: TType, props: Omit<TProps, 'children'>, children: string[]) {
const element = elements[type] as typeof elements[TType];
TProps extends BaseElementProps = Parameters<(typeof elements)[TType]>[0],
>(type: TType, props: Omit<TProps, "children">, children: string[]) {
const element = elements[type] as (typeof elements)[TType];
if (element) {
children = children.filter(Boolean);

return element({
...props,
children: type === 'code' ? children.join('\n') : children.join(''),
children: type === "code" ? children.join("\n") : children.join(""),
} as any);
}

return '';
return "";
}

function handleToken(token: Token): string {
if (token.type === 'selection') {
if (token.type === "selection") {
return h(
'selection',
"selection",
{ className: token.className },
token.selection.map((token) => handleToken(token))
token.selection.map((token) => handleToken(token)),
);
}
if (token.type === 'hat') {
return h('hat', token, [escapeHtml(token.content)]);
if (token.type === "hat") {
return h("hat", token, [escapeHtml(token.content)]);
}

const cssDeclarations = [`color: ${token.color || options.fg}`];
if (token.fontStyle && FontStyle.Italic) {
cssDeclarations.push('font-style: italic');
cssDeclarations.push("font-style: italic");
}
if (token.fontStyle && FontStyle.Bold) {
cssDeclarations.push('font-weight: bold');
cssDeclarations.push("font-weight: bold");
}
if (token.fontStyle && FontStyle.Underline) {
cssDeclarations.push('text-decoration: underline');
cssDeclarations.push("text-decoration: underline");
}

return h(
'token',
"token",
{
style: cssDeclarations.join('; '),
style: cssDeclarations.join("; "),
},
[escapeHtml(token.content)]
[escapeHtml(token.content)],
);
}

return h('pre', { className: 'shiki', style: `background-color: ${bg}` }, [
options.langId ? `<div class="language-id">${options.langId}</div>` : '',
return h("pre", { className: "shiki", style: `background-color: ${bg}` }, [
options.langId ? `<div class="language-id">${options.langId}</div>` : "",
h(
'code',
"code",
{},
lines.map((line, index) => {
const lineNumber = index + 1;
const lineOptions = optionsByLineNumber.get(lineNumber) ?? [];
const lineClasses = getLineClasses(lineOptions).join(' ');
const lineClasses = getLineClasses(lineOptions).join(" ");
return h(
'line',
"line",
{
className: lineClasses,
},
line.length === 0
? ['&nbsp;']
: line.map((token) => handleToken(token))
? ["&nbsp;"]
: line.map((token) => handleToken(token)),
);
})
}),
),
]);
}

const htmlEscapes = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#39;",
} as const;

function escapeHtml(html: string) {
return (html || '').replace(
return (html || "").replace(
/[&<>"']/g,
(chr) => htmlEscapes[chr as keyof typeof htmlEscapes]
(chr) => htmlEscapes[chr as keyof typeof htmlEscapes],
);
}

function getLineClasses(lineOptions: { classes?: string }[]) {
const lineClasses = new Set(['line']);
const lineClasses = new Set(["line"]);
for (const lineOption of lineOptions) {
for (const lineClass of lineOption.classes ?? []) {
lineClasses.add(lineClass);

0 comments on commit 8baf3e5

Please sign in to comment.