From b2dc0d9e665583890ca2ddfc213fd5f13a539c7f Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Sun, 7 Nov 2021 11:04:38 +0000 Subject: [PATCH 1/3] Dispose of decorations --- src/core/Decorations.ts | 20 +++++++++----- src/core/FontMeasurements.ts | 9 ++++--- src/core/editStyles.ts | 51 ++++++++++++++++++++++++++---------- src/extension.ts | 28 +++++++------------- src/typings/Types.ts | 4 +++ src/util/graphFactories.ts | 27 ++++++++++++++----- 6 files changed, 90 insertions(+), 49 deletions(-) diff --git a/src/core/Decorations.ts b/src/core/Decorations.ts index ac78512ec2..e24e6b425d 100644 --- a/src/core/Decorations.ts +++ b/src/core/Decorations.ts @@ -19,6 +19,7 @@ import { DEFAULT_HAT_HEIGHT_EM, DEFAULT_VERTICAL_OFFSET_EM, } from "./shapeAdjustments"; +import { Graph } from "../typings/Types"; export type DecorationMap = { [k in HatStyleName]?: vscode.TextEditorDecorationType; @@ -35,11 +36,9 @@ export default class Decorations { hatStyleMap!: Record; hatStyleNames!: HatStyleName[]; - constructor( - fontMeasurements: FontMeasurements, - private extensionPath: string - ) { - this.constructDecorations(fontMeasurements); + constructor(private graph: Graph) { + this.constructDecorations(graph.fontMeasurements); + graph.extensionContext.subscriptions.push(this); } destroyDecorations() { @@ -209,7 +208,12 @@ export default class Decorations { scaleFactor: number, hatVerticalOffsetEm: number ) { - const iconPath = join(this.extensionPath, "images", "hats", `${shape}.svg`); + const iconPath = join( + this.graph.extensionContext.extensionPath, + "images", + "hats", + `${shape}.svg` + ); const rawSvg = readFileSync(iconPath, "utf8"); const { characterWidth, characterHeight, fontSize } = fontMeasurements; @@ -272,4 +276,8 @@ export default class Decorations { return { originalViewBoxHeight, originalViewBoxWidth }; } + + dispose() { + this.destroyDecorations(); + } } diff --git a/src/core/FontMeasurements.ts b/src/core/FontMeasurements.ts index 467d6d474a..b10c1bbfa2 100644 --- a/src/core/FontMeasurements.ts +++ b/src/core/FontMeasurements.ts @@ -1,4 +1,5 @@ import * as vscode from "vscode"; +import { Graph } from "../typings/Types"; /** * Contains measurements for the user's font @@ -17,16 +18,16 @@ export default class FontMeasurements { */ characterHeight!: number; - constructor(private context: vscode.ExtensionContext) {} + constructor(private graph: Graph) {} clearCache() { - this.context.globalState.update("fontRatios", undefined); + this.graph.extensionContext.globalState.update("fontRatios", undefined); } async calculate() { const fontFamily = getFontFamily(); let widthRatio, heightRatio; - let fontRatiosCache = this.context.globalState.get<{ + let fontRatiosCache = this.graph.extensionContext.globalState.get<{ widthRatio: number; heightRatio: number; fontFamily: string; @@ -34,7 +35,7 @@ export default class FontMeasurements { if (fontRatiosCache == null || fontRatiosCache.fontFamily !== fontFamily) { const fontRatios = await getFontRatios(); - this.context.globalState.update("fontRatios", { + this.graph.extensionContext.globalState.update("fontRatios", { ...fontRatios, fontFamily, }); diff --git a/src/core/editStyles.ts b/src/core/editStyles.ts index b948c1127f..8788cf2a47 100644 --- a/src/core/editStyles.ts +++ b/src/core/editStyles.ts @@ -4,12 +4,13 @@ import { DecorationRangeBehavior, window, } from "vscode"; +import { Graph } from "../typings/Types"; export class EditStyle { token: TextEditorDecorationType; line: TextEditorDecorationType; - constructor(colorName: string) { + constructor(colorName: EditStyleThemeColorName) { const options = { backgroundColor: new ThemeColor(`cursorless.${colorName}`), rangeBehavior: DecorationRangeBehavior.ClosedClosed, @@ -20,20 +21,42 @@ export class EditStyle { isWholeLine: true, }); } + + dispose() { + this.token.dispose(); + this.line.dispose(); + } } -export class EditStyles { - pendingDelete: EditStyle; - referenced: EditStyle; - pendingModification0: EditStyle; - pendingModification1: EditStyle; - justAdded: EditStyle; - - constructor() { - this.pendingDelete = new EditStyle("pendingDeleteBackground"); - this.justAdded = new EditStyle("justAddedBackground"); - this.referenced = new EditStyle("referencedBackground"); - this.pendingModification0 = new EditStyle("pendingModification0Background"); - this.pendingModification1 = new EditStyle("pendingModification1Background"); +const EDIT_STYLE_NAMES = [ + "pendingDelete", + "referenced", + "pendingModification0", + "pendingModification1", + "justAdded", +] as const; + +type EditStyleName = typeof EDIT_STYLE_NAMES[number]; +type EditStyleThemeColorName = `${EditStyleName}Background`; + +export class EditStyles implements Record { + pendingDelete!: EditStyle; + referenced!: EditStyle; + pendingModification0!: EditStyle; + pendingModification1!: EditStyle; + justAdded!: EditStyle; + + constructor(graph: Graph) { + EDIT_STYLE_NAMES.forEach((editStyleName) => { + this[editStyleName] = new EditStyle(`${editStyleName}Background`); + }); + + graph.extensionContext.subscriptions.push(this); + } + + dispose() { + EDIT_STYLE_NAMES.forEach((editStyleName) => { + this[editStyleName].dispose(); + }); } } diff --git a/src/extension.ts b/src/extension.ts index 04e1d62fa2..5dedda004e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,17 +1,10 @@ import * as vscode from "vscode"; import { addDecorationsToEditors } from "./util/addDecorationsToEditor"; import { DECORATION_DEBOUNCE_DELAY } from "./core/constants"; -import Decorations from "./core/Decorations"; import graphFactories from "./util/graphFactories"; import inferFullTargets from "./core/inferFullTargets"; import processTargets from "./processTargets"; -import FontMeasurements from "./core/FontMeasurements"; -import { - ActionType, - Graph, - PartialTarget, - ProcessedTargetsContext, -} from "./typings/Types"; +import { Graph, PartialTarget, ProcessedTargetsContext } from "./typings/Types"; import makeGraph, { FactoryMap } from "./util/makeGraph"; import { logBranchTypes } from "./util/debug"; import { TestCase } from "./testUtil/TestCase"; @@ -19,13 +12,8 @@ import { ThatMark } from "./core/ThatMark"; import { TestCaseRecorder } from "./testUtil/TestCaseRecorder"; import { getParseTreeApi } from "./util/getExtensionApi"; import { canonicalizeAndValidateCommand } from "./util/canonicalizeAndValidateCommand"; -import canonicalizeActionName from "./util/canonicalizeActionName"; export async function activate(context: vscode.ExtensionContext) { - const fontMeasurements = new FontMeasurements(context); - await fontMeasurements.calculate(); - const decorations = new Decorations(fontMeasurements, context.extensionPath); - const { getNodeAtLocation } = await getParseTreeApi(); var isActive = vscode.workspace @@ -33,14 +21,14 @@ export async function activate(context: vscode.ExtensionContext) { .get("showOnStart")!; function clearEditorDecorations(editor: vscode.TextEditor) { - decorations.decorations.forEach(({ decoration }) => { + graph.decorations.decorations.forEach(({ decoration }) => { editor.setDecorations(decoration, []); }); } function addDecorations() { if (isActive) { - addDecorationsToEditors(graph.navigationMap, decorations); + addDecorationsToEditors(graph.navigationMap, graph.decorations); } else { vscode.window.visibleTextEditors.forEach(clearEditorDecorations); graph.navigationMap.clear(); @@ -72,7 +60,7 @@ export async function activate(context: vscode.ExtensionContext) { const recomputeDecorationStylesDisposable = vscode.commands.registerCommand( "cursorless.recomputeDecorationStyles", () => { - fontMeasurements.clearCache(); + graph.fontMeasurements.clearCache(); recomputeDecorationStyles(); } ); @@ -82,6 +70,8 @@ export async function activate(context: vscode.ExtensionContext) { extensionContext: () => context, } as FactoryMap); graph.snippets.init(); + await graph.fontMeasurements.calculate(); + const thatMark = new ThatMark(); const sourceMark = new ThatMark(); const testCaseRecorder = new TestCaseRecorder(context); @@ -248,9 +238,9 @@ export async function activate(context: vscode.ExtensionContext) { } const recomputeDecorationStyles = async () => { - decorations.destroyDecorations(); - await fontMeasurements.calculate(); - decorations.constructDecorations(fontMeasurements); + graph.decorations.destroyDecorations(); + await graph.fontMeasurements.calculate(); + graph.decorations.constructDecorations(graph.fontMeasurements); addDecorations(); }; diff --git a/src/typings/Types.ts b/src/typings/Types.ts index a3ec001b7b..55f4014ceb 100644 --- a/src/typings/Types.ts +++ b/src/typings/Types.ts @@ -7,6 +7,8 @@ import NavigationMap from "../core/NavigationMap"; import { Snippets } from "../core/Snippets"; import { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { FullRangeInfo } from "./updateSelections"; +import Decorations from "../core/Decorations"; +import FontMeasurements from "../core/FontMeasurements"; /** * A token within a text editor, including the current display line of the token @@ -354,6 +356,8 @@ export interface Graph { * as the document changes */ readonly rangeUpdater: RangeUpdater; + readonly decorations: Decorations; + readonly fontMeasurements: FontMeasurements; } export type NodeMatcherValue = { diff --git a/src/util/graphFactories.ts b/src/util/graphFactories.ts index 2ef76afff9..749ee71536 100644 --- a/src/util/graphFactories.ts +++ b/src/util/graphFactories.ts @@ -5,13 +5,28 @@ import { FactoryMap } from "./makeGraph"; import NavigationMap from "../core/NavigationMap"; import { Snippets } from "../core/Snippets"; import { RangeUpdater } from "../core/updateSelections/RangeUpdater"; +import Decorations from "../core/Decorations"; +import FontMeasurements from "../core/FontMeasurements"; -const graphFactories: Partial> = { - actions: (graph: Graph) => new Actions(graph), - editStyles: () => new EditStyles(), - navigationMap: (graph: Graph) => new NavigationMap(graph), - snippets: (graph: Graph) => new Snippets(graph), - rangeUpdater: (graph: Graph) => new RangeUpdater(graph), +type ConstructorMap = { + [P in keyof T]: new (t: T) => T[P]; }; +const graphConstructors: Partial> = { + actions: Actions, + editStyles: EditStyles, + navigationMap: NavigationMap, + decorations: Decorations, + fontMeasurements: FontMeasurements, + snippets: Snippets, + rangeUpdater: RangeUpdater, +}; + +const graphFactories: Partial> = Object.fromEntries( + Object.entries(graphConstructors).map(([key, constructor]) => [ + key, + (graph: Graph) => new constructor(graph), + ]) +); + export default graphFactories; From f4465e94eb7830d94c9a9fd3926f83fea17611da Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Sun, 7 Nov 2021 11:50:48 +0000 Subject: [PATCH 2/3] Ad more documentation --- src/typings/Types.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/typings/Types.ts b/src/typings/Types.ts index 55f4014ceb..ee026f2051 100644 --- a/src/typings/Types.ts +++ b/src/typings/Types.ts @@ -13,6 +13,7 @@ import FontMeasurements from "../core/FontMeasurements"; /** * A token within a text editor, including the current display line of the token */ + export interface Token extends FullRangeInfo { editor: vscode.TextEditor; displayLine: number; @@ -48,6 +49,7 @@ export interface LineNumberPosition { lineNumber: number; isRelative: boolean; } + export interface LineNumber { type: "lineNumber"; anchor: LineNumberPosition; @@ -62,6 +64,7 @@ export type Mark = // | LastCursorPosition Not implemented yet | DecoratedSymbol | LineNumber; + export type Delimiter = | "angleBrackets" | "backtickQuotes" @@ -99,6 +102,7 @@ export type ScopeType = | "xmlElement" | "xmlEndTag" | "xmlStartTag"; + export type SubTokenType = "word" | "character"; export interface SurroundingPairModifier { @@ -106,12 +110,14 @@ export interface SurroundingPairModifier { delimiter: Delimiter | null; delimitersOnly: boolean; } + export interface ContainingScopeModifier { type: "containingScope"; scopeType: ScopeType; valueOnly?: boolean; includeSiblings?: boolean; } + export interface SubTokenModifier { type: "subpiece"; pieceType: SubTokenType; @@ -120,15 +126,19 @@ export interface SubTokenModifier { excludeAnchor?: boolean; excludeActive?: boolean; } + export interface MatchingPairSymbolModifier { type: "matchingPairSymbol"; } + export interface IdentityModifier { type: "identity"; } + export interface HeadModifier { type: "head"; } + export interface TailModifier { type: "tail"; } @@ -145,7 +155,9 @@ export type Modifier = export type SelectionType = // | "character" Not implemented "token" | "line" | "notebookCell" | "paragraph" | "document"; + export type Position = "before" | "after" | "contents"; + export type InsideOutsideType = "inside" | "outside" | null; export interface PartialPrimitiveTarget { @@ -356,7 +368,15 @@ export interface Graph { * as the document changes */ readonly rangeUpdater: RangeUpdater; + + /** + * Responsible for all the hat styles + */ readonly decorations: Decorations; + + /** + * Takes measurements of the user's font + */ readonly fontMeasurements: FontMeasurements; } @@ -376,18 +396,21 @@ export type NodeMatcher = ( * Returns the desired relative of the provided node. * Returns null if matching node not found. **/ + export type NodeFinder = ( node: SyntaxNode, selection?: vscode.Selection ) => SyntaxNode | null; /** Returns one or more selections for a given SyntaxNode */ + export type SelectionExtractor = ( editor: vscode.TextEditor, nodes: SyntaxNode ) => SelectionWithContext; /** Represent a single edit/change in the document */ + export interface Edit { range: vscode.Range; text: string; From 9aeef304a7b3115484ee9ac9de44818c8f0ef98c Mon Sep 17 00:00:00 2001 From: Pokey Rule Date: Sun, 7 Nov 2021 11:52:08 +0000 Subject: [PATCH 3/3] Remove extra spaces --- src/typings/Types.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/typings/Types.ts b/src/typings/Types.ts index ee026f2051..fa6cf760b8 100644 --- a/src/typings/Types.ts +++ b/src/typings/Types.ts @@ -13,7 +13,6 @@ import FontMeasurements from "../core/FontMeasurements"; /** * A token within a text editor, including the current display line of the token */ - export interface Token extends FullRangeInfo { editor: vscode.TextEditor; displayLine: number; @@ -396,21 +395,18 @@ export type NodeMatcher = ( * Returns the desired relative of the provided node. * Returns null if matching node not found. **/ - export type NodeFinder = ( node: SyntaxNode, selection?: vscode.Selection ) => SyntaxNode | null; /** Returns one or more selections for a given SyntaxNode */ - export type SelectionExtractor = ( editor: vscode.TextEditor, nodes: SyntaxNode ) => SelectionWithContext; /** Represent a single edit/change in the document */ - export interface Edit { range: vscode.Range; text: string;