diff --git a/images/curved-hat.svg b/images/curved-hat.svg
deleted file mode 100644
index e38e5d5cd8..0000000000
--- a/images/curved-hat.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/images/diamond-hat.svg b/images/diamond-hat.svg
deleted file mode 100644
index 57009204e8..0000000000
--- a/images/diamond-hat.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/images/hats/chevron.svg b/images/hats/chevron.svg
new file mode 100644
index 0000000000..42239e8c9c
--- /dev/null
+++ b/images/hats/chevron.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/images/round-hat.svg b/images/hats/default.svg
similarity index 100%
rename from images/round-hat.svg
rename to images/hats/default.svg
diff --git a/images/hats/star.svg b/images/hats/star.svg
new file mode 100644
index 0000000000..3a08a22c93
--- /dev/null
+++ b/images/hats/star.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/core/Decorations.ts b/src/core/Decorations.ts
index 4f17dd9831..4d5ec1545f 100644
--- a/src/core/Decorations.ts
+++ b/src/core/Decorations.ts
@@ -1,20 +1,42 @@
import * as vscode from "vscode";
import { join } from "path";
-import { COLORS } from "./constants";
-import { SymbolColor } from "./constants";
+import {
+ HatStyleName,
+ HatShape,
+ hatStyleMap,
+ hatStyleNames,
+ HAT_SHAPES,
+} from "./constants";
import { readFileSync } from "fs";
import { DecorationColorSetting } from "../typings/Types";
import FontMeasurements from "./FontMeasurements";
-const DEFAULT_HAT_WIDTH_TO_CHARACTER_WITH_RATIO = 0.39;
-const DEFAULT_HAT_VERTICAL_OFFSET_EM = -0.05;
+interface ShapeMeasurements {
+ hatWidthToCharacterWidthRatio: number;
+ verticalOffsetEm: number;
+}
+
+const defaultShapeMeasurements: Record = {
+ default: {
+ hatWidthToCharacterWidthRatio: 0.507,
+ verticalOffsetEm: -0.05,
+ },
+ star: {
+ hatWidthToCharacterWidthRatio: 0.6825,
+ verticalOffsetEm: -0.105,
+ },
+ chevron: {
+ hatWidthToCharacterWidthRatio: 0.6825,
+ verticalOffsetEm: -0.12,
+ },
+};
export type DecorationMap = {
- [k in SymbolColor]?: vscode.TextEditorDecorationType;
+ [k in HatStyleName]?: vscode.TextEditorDecorationType;
};
export interface NamedDecoration {
- name: SymbolColor;
+ name: HatStyleName;
decoration: vscode.TextEditorDecorationType;
}
@@ -22,7 +44,10 @@ export default class Decorations {
decorations!: NamedDecoration[];
decorationMap!: DecorationMap;
- constructor(fontMeasurements: FontMeasurements) {
+ constructor(
+ fontMeasurements: FontMeasurements,
+ private extensionPath: string
+ ) {
this.constructDecorations(fontMeasurements);
}
@@ -43,23 +68,37 @@ export default class Decorations {
const hatScaleFactor = 1 + hatSizeAdjustment / 100;
- const { svg, svgWidthPx, svgHeightPx } = this.processSvg(
- fontMeasurements,
- hatScaleFactor * DEFAULT_HAT_WIDTH_TO_CHARACTER_WITH_RATIO,
- (DEFAULT_HAT_VERTICAL_OFFSET_EM + userHatVerticalOffsetAdjustment / 100) *
- fontMeasurements.fontSize
+ const hatSvgMap = Object.fromEntries(
+ HAT_SHAPES.map((shape) => {
+ const { hatWidthToCharacterWidthRatio, verticalOffsetEm } =
+ defaultShapeMeasurements[shape];
+
+ return [
+ shape,
+ this.processSvg(
+ fontMeasurements,
+ shape,
+ hatScaleFactor * hatWidthToCharacterWidthRatio,
+ (verticalOffsetEm + userHatVerticalOffsetAdjustment / 100) *
+ fontMeasurements.fontSize
+ ),
+ ];
+ })
);
- const spanWidthPx =
- svgWidthPx + (fontMeasurements.characterWidth - svgWidthPx) / 2;
+ this.decorations = hatStyleNames.map((styleName) => {
+ const { color, shape } = hatStyleMap[styleName];
+ const { svg, svgWidthPx, svgHeightPx } = hatSvgMap[shape];
+
+ const spanWidthPx =
+ svgWidthPx + (fontMeasurements.characterWidth - svgWidthPx) / 2;
- this.decorations = COLORS.map((color) => {
const colorSetting = vscode.workspace
.getConfiguration("cursorless.colors")
.get(color)!;
return {
- name: color,
+ name: styleName,
decoration: vscode.window.createTextEditorDecorationType({
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
light: {
@@ -121,10 +160,11 @@ export default class Decorations {
*/
private processSvg(
fontMeasurements: FontMeasurements,
+ shape: HatShape,
hatWidthToCharacterWidthRatio: number,
hatVerticalOffset: number
) {
- const iconPath = join(__dirname, "..", "images", "round-hat.svg");
+ const iconPath = join(this.extensionPath, "images", "hats", `${shape}.svg`);
const rawSvg = readFileSync(iconPath, "utf8");
const { originalViewBoxHeight, originalViewBoxWidth } =
diff --git a/src/core/NavigationMap.ts b/src/core/NavigationMap.ts
index 3484e80413..0b5688e391 100644
--- a/src/core/NavigationMap.ts
+++ b/src/core/NavigationMap.ts
@@ -1,9 +1,9 @@
import { TextDocumentChangeEvent, Range } from "vscode";
-import { SymbolColor } from "./constants";
+import { HatStyleName } from "./constants";
import { SelectionWithEditor, Token } from "../typings/Types";
/**
- * Maps from (color, character) pairs to tokens
+ * Maps from (hatStyle, character) pairs to tokens
*/
export default class NavigationMap {
updateTokenRanges(edit: TextDocumentChangeEvent) {
@@ -11,7 +11,7 @@ export default class NavigationMap {
// Amount by which to shift ranges
const shift = editComponent.text.length - editComponent.rangeLength;
- Object.entries(this.map).forEach(([coloredSymbol, token]) => {
+ Object.entries(this.map).forEach(([decoratedCharacter, token]) => {
if (token.editor.document !== edit.document) {
return;
}
@@ -22,7 +22,7 @@ export default class NavigationMap {
if (editComponent.range.end.isAfter(token.range.start)) {
// If there is overlap, we just delete the token
- delete this.map[coloredSymbol];
+ delete this.map[decoratedCharacter];
return;
}
@@ -40,24 +40,24 @@ export default class NavigationMap {
}
private map: {
- [coloredSymbol: string]: Token;
+ [decoratedCharacter: string]: Token;
} = {};
- static getKey(color: SymbolColor, character: string) {
- return `${color}.${character}`;
+ static getKey(hatStyle: HatStyleName, character: string) {
+ return `${hatStyle}.${character}`;
}
static splitKey(key: string) {
- const [color, character] = key.split(".");
- return { color: color as SymbolColor, character };
+ const [hatStyle, character] = key.split(".");
+ return { hatStyle: hatStyle as HatStyleName, character };
}
- public addToken(color: SymbolColor, character: string, token: Token) {
- this.map[NavigationMap.getKey(color, character)] = token;
+ public addToken(hatStyle: HatStyleName, character: string, token: Token) {
+ this.map[NavigationMap.getKey(hatStyle, character)] = token;
}
- public getToken(color: SymbolColor, character: string) {
- return this.map[NavigationMap.getKey(color, character)];
+ public getToken(hatStyle: HatStyleName, character: string) {
+ return this.map[NavigationMap.getKey(hatStyle, character)];
}
public clear() {
diff --git a/src/core/constants.ts b/src/core/constants.ts
index 3489e944f1..6f5bc6068b 100644
--- a/src/core/constants.ts
+++ b/src/core/constants.ts
@@ -2,7 +2,7 @@ export const SUBWORD_MATCHER = /[A-Z]?[a-z]+|[A-Z]+(?![a-z])|[0-9]+/g;
export const DEBOUNCE_DELAY = 175;
-export const COLORS = [
+const HAT_COLORS = [
"default",
"blue",
"green",
@@ -11,4 +11,30 @@ export const COLORS = [
"purple",
] as const;
-export type SymbolColor = typeof COLORS[number];
+const HAT_NON_DEFAULT_SHAPES = ["star", "chevron"] as const;
+export const HAT_SHAPES = [...HAT_NON_DEFAULT_SHAPES, "default"] as const;
+
+export type HatColor = typeof HAT_COLORS[number];
+export type HatShape = typeof HAT_SHAPES[number];
+type HatNonDefaultShape = typeof HAT_NON_DEFAULT_SHAPES[number];
+export type HatStyleName = HatColor | `${HatColor}-${HatNonDefaultShape}`;
+
+export interface HatStyle {
+ color: HatColor;
+ shape: HatShape;
+}
+
+export const hatStyleMap = {
+ ...Object.fromEntries(
+ HAT_COLORS.map((color) => [color, { color, shape: "default" }])
+ ),
+ ...Object.fromEntries(
+ HAT_COLORS.flatMap((color) =>
+ HAT_NON_DEFAULT_SHAPES.map((shape) => [
+ `${color}-${shape}`,
+ { color, shape },
+ ])
+ )
+ ),
+} as Record;
+export const hatStyleNames = Object.keys(hatStyleMap);
diff --git a/src/extension.ts b/src/extension.ts
index ee5abbcffb..1a71b2176f 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -23,7 +23,7 @@ import canonicalizeActionName from "./canonicalizeActionName";
export async function activate(context: vscode.ExtensionContext) {
const fontMeasurements = new FontMeasurements(context);
await fontMeasurements.calculate();
- const decorations = new Decorations(fontMeasurements);
+ const decorations = new Decorations(fontMeasurements, context.extensionPath);
const { getNodeAtLocation } = await getParseTreeApi();
diff --git a/src/test/suite/recorded.test.ts b/src/test/suite/recorded.test.ts
index 87d4774c00..9b7d9ddc13 100644
--- a/src/test/suite/recorded.test.ts
+++ b/src/test/suite/recorded.test.ts
@@ -93,9 +93,12 @@ async function runTest(file: string) {
// Assert that recorded decorations are present
Object.entries(fixture.marks).forEach(([key, token]) => {
- const { color, character } = NavigationMap.splitKey(key);
- const currentToken = cursorlessApi.navigationMap.getToken(color, character);
- assert(currentToken != null, `Mark "${color} ${character}" not found`);
+ const { hatStyle, character } = NavigationMap.splitKey(key);
+ const currentToken = cursorlessApi.navigationMap.getToken(
+ hatStyle,
+ character
+ );
+ assert(currentToken != null, `Mark "${hatStyle} ${character}" not found`);
assert.deepStrictEqual(rangeToPlainObject(currentToken.range), token);
});
diff --git a/src/testUtil/extractTargetedMarks.ts b/src/testUtil/extractTargetedMarks.ts
index 9743585f1d..83a9d81c17 100644
--- a/src/testUtil/extractTargetedMarks.ts
+++ b/src/testUtil/extractTargetedMarks.ts
@@ -1,4 +1,4 @@
-import { SymbolColor } from "../core/constants";
+import { HatStyleName } from "../core/constants";
import NavigationMap from "../core/NavigationMap";
import { PrimitiveTarget, Target, Token } from "../typings/Types";
@@ -33,14 +33,11 @@ export function extractTargetedMarks(
targets: Target[],
navigationMap: NavigationMap
) {
- const targetedMarks: { [coloredSymbol: string]: Token } = {};
+ const targetedMarks: { [decoratedCharacter: string]: Token } = {};
const targetKeys = targets.map(extractTargetKeys).flat();
targetKeys.forEach((key) => {
- const { color, character } = NavigationMap.splitKey(key);
- targetedMarks[key] = navigationMap.getToken(
- color as SymbolColor,
- character
- );
+ const { hatStyle, character } = NavigationMap.splitKey(key);
+ targetedMarks[key] = navigationMap.getToken(hatStyle, character);
});
return targetedMarks;
}
diff --git a/src/testUtil/toPlainObject.ts b/src/testUtil/toPlainObject.ts
index 23485b17b3..b5bc745e9b 100644
--- a/src/testUtil/toPlainObject.ts
+++ b/src/testUtil/toPlainObject.ts
@@ -16,7 +16,7 @@ export type SelectionPlainObject = {
active: PositionPlainObject;
};
-export type SerializedMarks = { [coloredSymbol: string]: RangePlainObject };
+export type SerializedMarks = { [decoratedCharacter: string]: RangePlainObject };
export function rangeToPlainObject(range: Range): RangePlainObject {
return {
@@ -38,7 +38,7 @@ export function positionToPlainObject(position: Position): PositionPlainObject {
return { line: position.line, character: position.character };
}
-export function marksToPlainObject(marks: { [coloredSymbol: string]: Token }) {
+export function marksToPlainObject(marks: { [decoratedCharacter: string]: Token }) {
const serializedMarks: SerializedMarks = {};
Object.entries(marks).forEach(
([key, value]: [string, Token]) =>
diff --git a/src/typings/Types.ts b/src/typings/Types.ts
index ec2779ab37..d4880b4a79 100644
--- a/src/typings/Types.ts
+++ b/src/typings/Types.ts
@@ -1,7 +1,7 @@
import { SyntaxNode } from "web-tree-sitter";
import * as vscode from "vscode";
import { Location } from "vscode";
-import { SymbolColor } from "../core/constants";
+import { HatStyleName } from "../core/constants";
import { EditStyles } from "../core/editStyles";
import NavigationMap from "../core/NavigationMap";
@@ -39,7 +39,7 @@ export interface LastCursorPosition {
export interface DecoratedSymbol {
type: "decoratedSymbol";
- symbolColor: SymbolColor;
+ symbolColor: HatStyleName;
character: string;
}
diff --git a/src/util/addDecorationsToEditor.ts b/src/util/addDecorationsToEditor.ts
index 17328c388b..a38f5a10ab 100644
--- a/src/util/addDecorationsToEditor.ts
+++ b/src/util/addDecorationsToEditor.ts
@@ -5,7 +5,7 @@ import { getTokenComparator as getTokenComparator } from "./getTokenComparator";
import { getTokensInRange } from "./getTokensInRange";
import { Token } from "../typings/Types";
import Decorations from "../core/Decorations";
-import { COLORS, SymbolColor } from "../core/constants";
+import { hatStyleNames, HatStyleName } from "../core/constants";
import NavigationMap from "../core/NavigationMap";
interface CharacterTokenInfo {
@@ -79,7 +79,7 @@ export function addDecorationsToEditors(
const decorationRanges: Map<
vscode.TextEditor,
{
- [decorationName in SymbolColor]?: vscode.Range[];
+ [decorationName in HatStyleName]?: vscode.Range[];
}
> = new Map(
editors.map((editor) => [
@@ -132,26 +132,29 @@ export function addDecorationsToEditors(
const currentDecorationIndex = bestCharacter.decorationIndex;
- const colorName = decorations.decorations[currentDecorationIndex].name;
+ const hatStyleName = decorations.decorations[currentDecorationIndex].name;
decorationRanges
.get(token.editor)!
- [colorName]!.push(
+ [hatStyleName]!.push(
new vscode.Range(
token.range.start.translate(undefined, bestCharacter.characterIdx),
token.range.start.translate(undefined, bestCharacter.characterIdx + 1)
)
);
- navigationMap.addToken(colorName, bestCharacter.character, token);
+ navigationMap.addToken(hatStyleName, bestCharacter.character, token);
characterDecorationIndices[bestCharacter.character] =
currentDecorationIndex + 1;
});
decorationRanges.forEach((ranges, editor) => {
- COLORS.forEach((color) => {
- editor.setDecorations(decorations.decorationMap[color]!, ranges[color]!);
+ hatStyleNames.forEach((hatStyleName) => {
+ editor.setDecorations(
+ decorations.decorationMap[hatStyleName]!,
+ ranges[hatStyleName]!
+ );
});
});
}