Skip to content

Scope visualizer #1523

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

Closed
wants to merge 62 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
63f7b24
Create the scope visualizer
pokey Jun 19, 2023
cc4996e
Add ScopeVisualizer tests
pokey Jun 19, 2023
02b8c43
Have separate `IdeScopeVisualizer` type
pokey Jun 21, 2023
97f85d7
Some fixes; just add scope visualizer function to ide
pokey Jun 21, 2023
c7ef0d1
more stuff
pokey Jun 21, 2023
a5dc6c0
More work towards simplified api
pokey Jun 22, 2023
8f31ae0
Working version of rewrite
pokey Jun 22, 2023
902b97b
Cleanup
pokey Jun 23, 2023
670b664
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jun 23, 2023
73d5aef
Revert unnecessary changes
pokey Jun 23, 2023
267b991
More cleanup
pokey Jun 23, 2023
bb74e27
More cleanup
pokey Jun 23, 2023
cc4d5e2
Fix brightness issue with layered scopes of same type
pokey Jun 23, 2023
65164d7
Update iteration default colors
pokey Jun 23, 2023
956969a
Switch to hex-8 colors
pokey Jun 23, 2023
336630e
cleanup
pokey Jun 23, 2023
97cdc5b
try to cleanup test
pokey Jun 23, 2023
800d7d2
more cleanup
pokey Jun 23, 2023
94236ef
more cleanup
pokey Jun 23, 2023
84eeeb9
more cleanup
pokey Jun 23, 2023
35efcb9
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jun 23, 2023
f72b846
more cleanup
pokey Jun 25, 2023
ebfc1bf
Fix bug with changing language id
pokey Jun 26, 2023
4452246
Simplification
pokey Jun 28, 2023
7b6f38f
cleanup
pokey Jun 28, 2023
32d819a
more cleanup
pokey Jun 28, 2023
51b6ba6
Cleanup
pokey Jul 5, 2023
b369801
more cleanup
pokey Jul 5, 2023
dcee4d0
more cleanup
pokey Jul 5, 2023
cd5c9ac
Add vscode api mocking support
pokey Jul 5, 2023
0ae8db3
Initial test work
pokey Jul 5, 2023
95caa5e
Working basic content range test
pokey Jul 5, 2023
3e9e459
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jul 5, 2023
b396efb
Use entire vscode namespaces rather than individual functions
pokey Jul 6, 2023
1143080
More cleanup
pokey Jul 6, 2023
2fc3f1a
cleanup tests
pokey Jul 10, 2023
a2d3446
More testing
pokey Jul 10, 2023
9f91428
Remove provider test
pokey Jul 10, 2023
1b87b8c
Add light colors
pokey Jul 10, 2023
853d6ca
Make spoken forms customizable
pokey Jul 13, 2023
0fdde7f
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Jul 13, 2023
c9ef7f6
Remove unused action
pokey Jul 13, 2023
f0ca29f
Reverts by changes
pokey Jul 13, 2023
1e0fa30
cleanup
pokey Jul 13, 2023
583eedc
Docs
pokey Jul 13, 2023
a155a18
Merge branch 'main' into pokey/scope-visualizer
pokey Jul 13, 2023
8139ac0
Add tests
pokey Jul 13, 2023
c4c8419
Cleanup
pokey Jul 13, 2023
a21fedc
Add ja stocks
pokey Jul 13, 2023
2b370ee
River run test subset
pokey Jul 13, 2023
5869d76
Tweaks
pokey Jul 13, 2023
a13fe00
Only use generalized ranges where necessary
pokey Jul 13, 2023
7b8e38b
cleanup
pokey Jul 14, 2023
bc4c816
Convert vscodeApi to singleton
pokey Jul 14, 2023
325a995
More docs
pokey Jul 14, 2023
1861f62
Add tests for handleMultipleLInes
pokey Jul 16, 2023
e8bc2bc
Compactify handleMultipleLines tests
pokey Jul 16, 2023
b43c13d
Cleanup; more tests
pokey Jul 16, 2023
733b1ed
JSDoc
pokey Jul 16, 2023
3fe5390
Tweak
pokey Jul 16, 2023
5f9a30d
Remove broken image embeds from JSDocs
pokey Jul 16, 2023
960fce2
try bumping stack size to fix CI
pokey Jul 16, 2023
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
5 changes: 5 additions & 0 deletions cursorless-talon/src/cursorless.talon
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,8 @@ tag: user.cursorless
user.cursorless_wrap(cursorless_wrap_action, cursorless_target, cursorless_wrapper)

{user.cursorless_homophone} settings: user.cursorless_show_settings_in_ide()

{user.cursorless_show_scope_visualizer} <user.cursorless_scope_type> [{user.cursorless_visualization_type}]:
user.private_cursorless_show_scope_visualizer(cursorless_scope_type, cursorless_visualization_type or "content")
{user.cursorless_hide_scope_visualizer}:
user.private_cursorless_hide_scope_visualizer()
49 changes: 49 additions & 0 deletions cursorless-talon/src/scope_visualizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from talon import Module, app

from .csv_overrides import init_csv_and_watch_changes
from .cursorless_command_server import run_rpc_command_no_wait

mod = Module()
mod.list("cursorless_show_scope_visualizer", desc="Show scope visualizer")
mod.list("cursorless_hide_scope_visualizer", desc="Hide scope visualizer")
mod.list(
"cursorless_visualization_type",
desc='Cursorless visualization type, e.g. "removal" or "iteration"',
)

# NOTE: Please do not change these dicts. Use the CSVs for customization.
# See https://www.cursorless.org/docs/user/customization/
visualization_types = {
"removal": "removal",
"iteration": "iteration",
"content": "content",
}


@mod.action_class
class Actions:
def private_cursorless_show_scope_visualizer(
scope_type: dict, visualization_type: str
):
"""Shows scope visualizer"""
run_rpc_command_no_wait(
"cursorless.showScopeVisualizer", scope_type, visualization_type
)

def private_cursorless_hide_scope_visualizer():
"""Hides scope visualizer"""
run_rpc_command_no_wait("cursorless.hideScopeVisualizer")


def on_ready():
init_csv_and_watch_changes(
"scope_visualizer",
{
"show_scope_visualizer": {"visualize": "showScopeVisualizer"},
"hide_scope_visualizer": {"visualize nothing": "hideScopeVisualizer"},
"visualization_type": visualization_types,
},
)


app.register("ready", on_ready)
8 changes: 8 additions & 0 deletions packages/common/src/cursorlessCommandIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export const cursorlessCommandIds = [
"cursorless.showQuickPick",
"cursorless.takeSnapshot",
"cursorless.toggleDecorations",
"cursorless.showScopeVisualizer",
"cursorless.hideScopeVisualizer",
] as const satisfies readonly `cursorless.${string}`[];

export type CursorlessCommandId = (typeof cursorlessCommandIds)[number];
Expand Down Expand Up @@ -104,4 +106,10 @@ export const cursorlessCommandDescriptions: Record<
["cursorless.keyboard.modal.modeToggle"]: new HiddenCommand(
"Toggle the cursorless modal mode",
),
["cursorless.showScopeVisualizer"]: new HiddenCommand(
"Show the scope visualizer",
),
["cursorless.hideScopeVisualizer"]: new HiddenCommand(
"Hide the scope visualizer",
),
};
2 changes: 2 additions & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export { default as DefaultMap } from "./util/DefaultMap";
export * from "./types/GeneralizedRange";
export * from "./types/RangeOffsets";
export * from "./util/omitByDeep";
export * from "./util/range";
export * from "./testUtil/isTesting";
export * from "./testUtil/testConstants";
export * from "./testUtil/getFixturePaths";
Expand Down Expand Up @@ -84,3 +85,4 @@ export * from "./extensionDependencies";
export * from "./getFakeCommandServerApi";
export * from "./types/TestCaseFixture";
export * from "./util/getEnvironmentVariableStrict";
export * from "./util/CompositeKeyDefaultMap";
22 changes: 18 additions & 4 deletions packages/common/src/testUtil/toPlainObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import type {
} from "..";
import { FlashStyle, isLineRange } from "..";
import { Token } from "../types/Token";
import { Position } from "../types/Position";
import { Range } from "../types/Range";
import { Selection } from "../types/Selection";

export type PositionPlainObject = {
Expand Down Expand Up @@ -85,7 +83,23 @@ export type SerializedMarks = {
[decoratedCharacter: string]: RangePlainObject;
};

export function rangeToPlainObject(range: Range): RangePlainObject {
/**
* Simplified Position interface containing only what we need for serialization
*/
interface SimplePosition {
line: number;
character: number;
}

/**
* Simplified Range interface containing only what we need for serialization
*/
interface SimpleRange {
start: SimplePosition;
end: SimplePosition;
}

export function rangeToPlainObject(range: SimpleRange): RangePlainObject {
return {
start: positionToPlainObject(range.start),
end: positionToPlainObject(range.end),
Expand All @@ -104,7 +118,7 @@ export function selectionToPlainObject(
export function positionToPlainObject({
line,
character,
}: Position): PositionPlainObject {
}: SimplePosition): PositionPlainObject {
return { line, character };
}

Expand Down
88 changes: 88 additions & 0 deletions packages/common/src/types/GeneralizedRange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,91 @@ export function toLineRange(range: Range): LineRange {
export function toCharacterRange({ start, end }: Range): CharacterRange {
return { type: "character", start, end };
}

export function isGeneralizedRangeEqual(
a: GeneralizedRange,
b: GeneralizedRange,
): boolean {
if (a.type === "character" && b.type === "character") {
return a.start.isEqual(b.start) && a.end.isEqual(b.end);
}

if (a.type === "line" && b.type === "line") {
return a.start === b.start && a.end === b.end;
}

return false;
}

/**
* Determines whether {@link a} contains {@link b}. This is true if {@link a}
* starts before or equal to the start of {@link b} and ends after or equal to
* the end of {@link b}.
*
* Note that if {@link a} is a {@link CharacterRange} and {@link b} is a
* {@link LineRange}, we require that the {@link LineRange} is fully contained
* in the {@link CharacterRange}, because otherwise it visually looks like the
* {@link LineRange} is not contained because the line range extends to the edge
* of the screen.
* @param a A generalized range
* @param b A generalized range
* @returns `true` if `a` contains `b`, `false` otherwise
*/
export function generalizedRangeContains(
a: GeneralizedRange,
b: GeneralizedRange,
): boolean {
if (a.type === "character") {
if (b.type === "character") {
// a.type === "character" && b.type === "character"
return a.start.isBeforeOrEqual(b.start) && a.end.isAfterOrEqual(b.end);
}

// a.type === "character" && b.type === "line"
// Require that the line range is fully contained in the character range
// because otherwise it visually looks like the line range is not contained
return a.start.line < b.start && a.end.line > b.end;
}

if (b.type === "line") {
// a.type === "line" && b.type === "line"
return a.start <= b.start && a.end >= b.end;
}

// a.type === "line" && b.type === "character"
return a.start <= b.start.line && a.end >= b.end.line;
}

/**
* Determines whether {@link a} touches {@link b}. This is true if {@link a}
* has any intersection with {@link b}, even if the intersection is empty.
*
* In the case where one range is a {@link CharacterRange} and the other is a
* {@link LineRange}, we return `true` if they both include at least one line
* in common.
* @param a A generalized range
* @param b A generalized range
* @returns `true` if `a` touches `b`, `false` otherwise
*/
export function generalizedRangeTouches(
a: GeneralizedRange,
b: GeneralizedRange,
): boolean {
if (a.type === "character") {
if (b.type === "character") {
// a.type === "character" && b.type === "character"
return a.start.isBeforeOrEqual(b.end) && a.end.isAfterOrEqual(b.start);
}

// a.type === "character" && b.type === "line"
return a.start.line <= b.end && a.end.line >= b.start;
}

if (b.type === "line") {
// a.type === "line" && b.type === "line"
return a.start <= b.end && a.end >= b.start;
}

// a.type === "line" && b.type === "character"
return a.start <= b.end.line && a.end >= b.start.line;
}
163 changes: 163 additions & 0 deletions packages/common/src/types/generalizedRangeContains.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import assert = require("assert");
import { generalizedRangeContains, Position } from "..";

suite("generalizedRangeContains", () => {
test("character", () => {
assert.strictEqual(
generalizedRangeContains(
{
type: "character",
start: new Position(0, 0),
end: new Position(0, 0),
},
{
type: "character",
start: new Position(0, 0),
end: new Position(0, 0),
},
),
true,
);
assert.strictEqual(
generalizedRangeContains(
{
type: "character",
start: new Position(0, 0),
end: new Position(0, 1),
},
{
type: "character",
start: new Position(0, 0),
end: new Position(0, 0),
},
),
true,
);
assert.strictEqual(
generalizedRangeContains(
{
type: "character",
start: new Position(0, 0),
end: new Position(0, 0),
},
{
type: "character",
start: new Position(0, 0),
end: new Position(0, 1),
},
),
false,
);
});

test("line", () => {
assert.strictEqual(
generalizedRangeContains(
{
type: "line",
start: 0,
end: 0,
},
{
type: "line",
start: 0,
end: 0,
},
),
true,
);
assert.strictEqual(
generalizedRangeContains(
{
type: "line",
start: 0,
end: 1,
},
{
type: "line",
start: 0,
end: 0,
},
),
true,
);
assert.strictEqual(
generalizedRangeContains(
{
type: "line",
start: 0,
end: 0,
},
{
type: "line",
start: 1,
end: 1,
},
),
false,
);
});

test("mixed", () => {
assert.strictEqual(
generalizedRangeContains(
{
type: "line",
start: 0,
end: 1,
},
{
type: "character",
start: new Position(0, 0),
end: new Position(1, 1),
},
),
true,
);
assert.strictEqual(
generalizedRangeContains(
{
type: "line",
start: 0,
end: 0,
},
{
type: "character",
start: new Position(0, 0),
end: new Position(1, 0),
},
),
false,
);
assert.strictEqual(
generalizedRangeContains(
{
type: "character",
start: new Position(0, 0),
end: new Position(2, 0),
},
{
type: "line",
start: 1,
end: 1,
},
),
true,
);
assert.strictEqual(
generalizedRangeContains(
{
type: "character",
start: new Position(0, 0),
end: new Position(1, 0),
},
{
type: "line",
start: 1,
end: 1,
},
),
false,
);
});
});
Loading