Skip to content

Commit 620afba

Browse files
committed
Add zero-based position features
1 parent 3342819 commit 620afba

File tree

7 files changed

+239
-12
lines changed

7 files changed

+239
-12
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66

77
It's not too smart yet, but:
88

9-
- it lets you choose a test file and re-run that file easily from anywhere in your editor.
9+
- It lets you choose a test file and re-run that file easily from anywhere in your editor.
1010
- It adds syntax highlighting for .types and .symbol files in a way which won't fill your screen with errors.
11+
- It adds a command and an optional status bar indicator for the selection’s zero-based position in the current file
1112

1213
- Symbol files ![](./screenshots/symbols.png)
1314

package.json

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"Other"
1616
],
1717
"activationEvents": [
18-
"workspaceContains:**/src/compiler/checker.ts"
18+
"workspaceContains:**/*.{ts,js,tsx,jsx}"
1919
],
2020
"main": "./out/extension.js",
2121
"contributes": {
@@ -48,15 +48,45 @@
4848
"commands": [
4949
{
5050
"command": "io.orta.typescript-dev.declare-current-test-file",
51-
"title": "TSC: Declare current test file",
51+
"title": "Declare current test file",
5252
"category": "TSC"
5353
},
5454
{
5555
"command": "io.orta.typescript-dev.run-current-test-file",
56-
"title": "TSC: Run tests",
56+
"title": "Run tests",
57+
"category": "TSC"
58+
},
59+
{
60+
"command": "io.orta.typescript-dev.go-to-position",
61+
"title": "Go to position",
62+
"category": "TSC"
63+
},
64+
{
65+
"command": "io.orta.typescript-dev.toggle-position",
66+
"title": "Toggle position indicator in status bar",
5767
"category": "TSC"
5868
}
59-
]
69+
],
70+
"menus": {
71+
"commandPalette": [
72+
{
73+
"command": "io.orta.typescript-dev.declare-current-test-file",
74+
"when": "io.orta.typescript-dev.isTypeScriptCodeBase"
75+
},
76+
{
77+
"command": "io.orta.typescript-dev.run-current-test-file",
78+
"when": "io.orta.typescript-dev.isTypeScriptCodeBase"
79+
},
80+
{
81+
"command": "io.orta.typescript-dev.go-to-position",
82+
"when": "editorFocus"
83+
},
84+
{
85+
"command": "io.orta.typescript-dev.toggle-position",
86+
"when": "editorFocus"
87+
}
88+
]
89+
}
6090
},
6191
"scripts": {
6292
"vscode:prepublish": "yarn run compile",
@@ -72,8 +102,8 @@
72102
"@types/vscode": "^1.36.0",
73103
"glob": "^7.1.4",
74104
"mocha": "^6.1.4",
75-
"typescript": "^3.3.1",
76105
"tslint": "^5.12.1",
106+
"typescript": "^4.0.0-dev.20200803",
77107
"vscode-test": "^1.0.0-next.0"
78108
}
79-
}
109+
}

src/context.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as vscode from "vscode"
2+
3+
export function setTypeScriptCodeBaseContext() {
4+
const isTypeScriptCodeBase = !!vscode.workspace.workspaceFolders?.some(f => f.name === "TypeScript")
5+
vscode.commands.executeCommand("setContext", "io.orta.typescript-dev.isTypeScriptCodeBase", isTypeScriptCodeBase)
6+
}

src/extension.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import * as vscode from "vscode"
22
import { basename } from "path"
3+
import { activatePosition } from "./position"
4+
import { setTypeScriptCodeBaseContext } from "./context"
35

46
export function activate(context: vscode.ExtensionContext) {
57
const funcs = handleTestFiles()
@@ -9,6 +11,10 @@ export function activate(context: vscode.ExtensionContext) {
911
let disposable2 = vscode.commands.registerCommand("io.orta.typescript-dev.run-current-test-file", funcs.run)
1012
context.subscriptions.push(disposable2)
1113

14+
context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(setTypeScriptCodeBaseContext));
15+
setTypeScriptCodeBaseContext();
16+
activatePosition(context)
17+
1218
// https://code.visualstudio.com/api/extension-guides/task-provider
1319
vscode.tasks.registerTaskProvider("tsc-dev", {
1420
provideTasks: async () => {
@@ -107,4 +113,4 @@ const handleTestFiles = () => {
107113
}
108114

109115
// this method is called when your extension is deactivated
110-
export function deactivate() {}
116+
export function deactivate() { }

src/position.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import * as vscode from "vscode";
2+
3+
const goToPositionCommandName = "io.orta.typescript-dev.go-to-position";
4+
const togglePositionCommandName = "io.orta.typescript-dev.toggle-position";
5+
6+
export function activatePosition(context: vscode.ExtensionContext) {
7+
context.subscriptions.push(vscode.commands.registerCommand(goToPositionCommandName, async () => {
8+
const expr = await vscode.window.showInputBox({
9+
prompt: "Zero-based position or comma-separated range. Addition and subtraction expressions are allowed."
10+
});
11+
12+
if (vscode.window.activeTextEditor && expr) {
13+
const range = tryParseRangeExpression(expr);
14+
if (range !== undefined && vscode.window.activeTextEditor) {
15+
const start = vscode.window.activeTextEditor.document.positionAt(range[0]);
16+
const end = range[1] === undefined ? start : vscode.window.activeTextEditor.document.positionAt(range[1]);
17+
vscode.window.activeTextEditor.selection = new vscode.Selection(start, end);
18+
}
19+
20+
vscode.window.activeTextEditor.revealRange(vscode.window.activeTextEditor.selection, vscode.TextEditorRevealType.InCenterIfOutsideViewport);
21+
}
22+
}));
23+
24+
let positionStatusBarItem: vscode.StatusBarItem;
25+
let positionStatusBarItemVisible = false; // This seems to be private state of `StatusBarItem`, so I have to track it myself?
26+
context.subscriptions.push(vscode.commands.registerCommand(togglePositionCommandName, () => {
27+
if (!positionStatusBarItem) {
28+
positionStatusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100.45);
29+
positionStatusBarItem.command = goToPositionCommandName;
30+
context.subscriptions.push(positionStatusBarItem);
31+
context.subscriptions.push(vscode.window.onDidChangeActiveTextEditor(() => updatePosition(positionStatusBarItem)));
32+
context.subscriptions.push(vscode.window.onDidChangeTextEditorSelection(() => updatePosition(positionStatusBarItem)));
33+
updatePosition(positionStatusBarItem);
34+
}
35+
36+
if (positionStatusBarItemVisible) {
37+
positionStatusBarItem.hide();
38+
} else {
39+
positionStatusBarItem.show();
40+
}
41+
positionStatusBarItemVisible = !positionStatusBarItemVisible;
42+
}));
43+
}
44+
45+
function updatePosition(statusBarItem: vscode.StatusBarItem) {
46+
const focus = vscode.window.activeTextEditor?.document.offsetAt(vscode.window.activeTextEditor.selection.active);
47+
const anchor = vscode.window.activeTextEditor?.document.offsetAt(vscode.window.activeTextEditor.selection.anchor);
48+
if (focus !== undefined && anchor !== undefined) {
49+
const lower = Math.min(focus, anchor);
50+
const upper = Math.max(focus, anchor);
51+
statusBarItem.text = "Pos " + lower + (upper === lower ? "" : `, ${upper}`);
52+
}
53+
}
54+
55+
export function tryParseRangeExpression(expr: string): readonly [number, number | undefined] | undefined {
56+
const [startExpr, endExpr] = expr.split(",");
57+
const start = tryParsePositionExpression(startExpr);
58+
if (start === undefined) return undefined;
59+
return [start, endExpr === undefined ? endExpr : tryParsePositionExpression(endExpr)];
60+
}
61+
62+
function tryParsePositionExpression(expr: string): number | undefined {
63+
const enum State {
64+
ParseOperator,
65+
ParseOperand,
66+
}
67+
68+
let state = State.ParseOperand as State;
69+
let value: number | undefined;
70+
let operator: "+" | "-" = "+";
71+
let pos = 0;
72+
73+
while (pos < expr.length) {
74+
skipSpace();
75+
const current = expr[pos];
76+
if (!current) break;
77+
78+
switch (state) {
79+
case State.ParseOperand:
80+
if (isNumeric(current)) {
81+
value = operate(consumeInt());
82+
state = State.ParseOperator;
83+
continue;
84+
} else if (current === "+") {
85+
pos++;
86+
continue;
87+
} else if (current === "-") {
88+
flipSign();
89+
pos++;
90+
continue;
91+
} else {
92+
return undefined;
93+
}
94+
case State.ParseOperator:
95+
if (current === "+") {
96+
operator = "+";
97+
pos++;
98+
state = State.ParseOperand;
99+
} else if (current === "-") {
100+
operator = "-";
101+
pos++;
102+
state = State.ParseOperand;
103+
} else {
104+
return undefined;
105+
}
106+
}
107+
}
108+
return value !== undefined && value >= 0 ? value : undefined;
109+
110+
function consumeInt(): number | undefined {
111+
let start = pos;
112+
while (pos < expr.length && isNumeric(expr[pos])) {
113+
pos++;
114+
}
115+
const int = +expr.slice(start, pos);
116+
if (Number.isSafeInteger(int)) {
117+
return int;
118+
}
119+
}
120+
121+
function skipSpace() {
122+
while (pos < expr.length && /\s/.test(expr[pos])) {
123+
pos++;
124+
}
125+
}
126+
127+
function operate(operand: number | undefined) {
128+
if (operand === undefined) return value;
129+
if (value === undefined) value = 0;
130+
switch (operator) {
131+
case "+": return value + operand;
132+
case "-": return value - operand;
133+
}
134+
}
135+
136+
function flipSign() {
137+
if (operator === "+") {
138+
operator = "-";
139+
} else {
140+
operator = "+";
141+
}
142+
}
143+
}
144+
145+
function isNumeric(char: string) {
146+
const code = char.charCodeAt(0);
147+
return 48 <= code && code <= 57;
148+
}

src/test/suite/position.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import * as assert from "assert";
2+
import { tryParseRangeExpression } from "../../position";
3+
4+
suite("Go To Position", () => {
5+
test("parses simple expressions", () => {
6+
assert.deepStrictEqual(tryParseRangeExpression("3"), [3, undefined]);
7+
assert.deepStrictEqual(tryParseRangeExpression("422424"), [422424, undefined]);
8+
assert.deepStrictEqual(tryParseRangeExpression(" 7 "), [7, undefined]);
9+
});
10+
11+
test("parses +/- arithmetic", () => {
12+
assert.deepStrictEqual(tryParseRangeExpression("3+5"), [8, undefined]);
13+
assert.deepStrictEqual(tryParseRangeExpression("3-1"), [2, undefined]);
14+
assert.deepStrictEqual(tryParseRangeExpression("3 - 1 + 4"), [6, undefined]);
15+
assert.deepStrictEqual(tryParseRangeExpression("+3 + -1"), [2, undefined]);
16+
assert.deepStrictEqual(tryParseRangeExpression("3--1"), [4, undefined]);
17+
assert.deepStrictEqual(tryParseRangeExpression("- 6 + + 7"), [1, undefined]);
18+
});
19+
20+
test("rejects bad input", () => {
21+
assert.deepStrictEqual(tryParseRangeExpression("3 * 1"), undefined);
22+
assert.deepStrictEqual(tryParseRangeExpression("-1"), undefined);
23+
assert.deepStrictEqual(tryParseRangeExpression("a"), undefined);
24+
assert.deepStrictEqual(tryParseRangeExpression("0x23"), undefined);
25+
assert.deepStrictEqual(tryParseRangeExpression(", 24"), undefined);
26+
assert.deepStrictEqual(tryParseRangeExpression("throw new Error()"), undefined);
27+
});
28+
29+
test("parses ranges", () => {
30+
assert.deepStrictEqual(tryParseRangeExpression("3, 7"), [3, 7]);
31+
assert.deepStrictEqual(tryParseRangeExpression("3 + 1 , 7 - 1"), [4, 6]);
32+
assert.deepStrictEqual(tryParseRangeExpression("3,4,5"), [3, 4]);
33+
assert.deepStrictEqual(tryParseRangeExpression("3,,,,4"), [3, undefined]);
34+
assert.deepStrictEqual(tryParseRangeExpression("3, 2"), [3, 2]);
35+
});
36+
});

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -869,10 +869,10 @@ tsutils@^2.29.0:
869869
dependencies:
870870
tslib "^1.8.1"
871871

872-
typescript@^3.3.1:
873-
version "3.5.3"
874-
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
875-
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==
872+
typescript@^4.0.0-dev.20200803:
873+
version "4.0.0-dev.20200803"
874+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.0-dev.20200803.tgz#ea8b0e9fb2ee3085598ff200c8568f04f4cbb2ba"
875+
integrity sha512-f/jDkFqCs0gbUd5MCUijO9u3AOMx1x1HdRDDHSidlc6uPVEkRduxjeTFhIXbGutO7ivzv+aC2sxH+1FQwsyBcg==
876876

877877
vscode-test@^1.0.0-next.0:
878878
version "1.0.0"

0 commit comments

Comments
 (0)