Skip to content

Commit 90ca6ac

Browse files
committed
start on hat stats and golden tests
1 parent 0f5d011 commit 90ca6ac

File tree

15 files changed

+1973
-0
lines changed

15 files changed

+1973
-0
lines changed

packages/common/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,7 @@ export * from "./getFakeCommandServerApi";
8686
export * from "./types/TestCaseFixture";
8787
export * from "./util/getEnvironmentVariableStrict";
8888
export * from "./util/CompositeKeyDefaultMap";
89+
export {
90+
InMemoryTextDocument,
91+
InMemoryTextEditor,
92+
} from "./testUtil/mockEditor";
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as assert from "assert";
2+
import { InMemoryTextDocument, Range } from "..";
3+
4+
suite("mockEditor", () => {
5+
test("basic", () => {
6+
const s = "abc\n\n123\n";
7+
const doc: InMemoryTextDocument = new InMemoryTextDocument("test.txt", s);
8+
9+
for (let i = 0; i < s.length; i++) {
10+
const pos = doc.positionAt(i);
11+
const offset = doc.offsetAt(pos);
12+
assert.equal(offset, i);
13+
}
14+
const line0 = doc.lineAt(0);
15+
assert.equal(line0.text, "abc");
16+
assert.equal(line0.firstNonWhitespaceCharacterIndex, 0);
17+
assert.equal(line0.isEmptyOrWhitespace, false);
18+
assert.equal(line0.lineNumber, 0);
19+
assert.ok(line0.range.isEqual(new Range(0, 0, 0, 2)));
20+
assert.equal(line0.rangeIncludingLineBreak.start.character, 0);
21+
assert.equal(line0.lastNonWhitespaceCharacterIndex, 2);
22+
23+
const line1 = doc.lineAt(1);
24+
assert.equal(line1.text, "");
25+
assert.equal(line1.firstNonWhitespaceCharacterIndex, 0);
26+
assert.equal(line1.isEmptyOrWhitespace, true);
27+
assert.equal(line1.lineNumber, 1);
28+
assert.ok(line1.range.isEqual(new Range(1, 0, 1, 0)));
29+
assert.equal(line1.rangeIncludingLineBreak.start.character, 0);
30+
assert.equal(line1.lastNonWhitespaceCharacterIndex, 0);
31+
});
32+
});
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
import { URI } from "vscode-uri";
2+
import {
3+
EndOfLine,
4+
Position,
5+
Range,
6+
Selection,
7+
TextEditor,
8+
TextEditorOptions,
9+
TextLine,
10+
} from "..";
11+
import { TextDocument } from "../types/TextDocument";
12+
13+
export class InMemoryTextLine implements TextLine {
14+
/**
15+
* The zero-based line number.
16+
*/
17+
readonly lineNumber: number;
18+
19+
/**
20+
* The text of this line without the line separator characters.
21+
*/
22+
readonly text: string;
23+
24+
/**
25+
* The eol character(s) of this line: "\n" or "\r\n".
26+
*/
27+
private eol: string;
28+
29+
constructor(lineNumber: number, text: string, eol: string) {
30+
if (lineNumber < 0) {
31+
throw new Error("lineNumber must be non-negative");
32+
}
33+
this.lineNumber = lineNumber;
34+
// TODO: validate text
35+
this.text = text;
36+
if (eol !== "\n" && eol !== "\r\n") {
37+
throw new Error("eol must be \\n or \\r\\n");
38+
}
39+
this.eol = eol;
40+
}
41+
42+
/**
43+
* The range this line covers without the line separator characters.
44+
*/
45+
get range(): Range {
46+
return new Range(this.lineNumber, 0, this.lineNumber, this.text.length);
47+
}
48+
49+
/**
50+
* The range this line covers with the line separator characters.
51+
*/
52+
get rangeIncludingLineBreak(): Range {
53+
return new Range(
54+
this.lineNumber,
55+
0,
56+
this.lineNumber,
57+
this.text.length + this.eol.length,
58+
);
59+
}
60+
61+
/**
62+
* The offset of the first character which is not a whitespace character as defined
63+
* by `/\s/`. **Note** that if a line is all whitespace the length of the line is returned.
64+
*/
65+
get firstNonWhitespaceCharacterIndex(): number {
66+
const idx = this.text.search(/\S/);
67+
return idx === -1 ? this.text.length : idx;
68+
}
69+
70+
/**
71+
* The offset of the last character which is not a whitespace character as defined
72+
* by `/\s/`. **Note** that if a line is all whitespace 0 is returned.
73+
*/
74+
get lastNonWhitespaceCharacterIndex(): number {
75+
const matches = this.text.match(/\S/g);
76+
if (!matches) {
77+
return 0;
78+
}
79+
return this.text.lastIndexOf(matches[matches.length - 1]);
80+
}
81+
82+
/**
83+
* Whether this line is whitespace only, shorthand
84+
* for {@link TextLine.firstNonWhitespaceCharacterIndex} === {@link TextLine.text TextLine.text.length}.
85+
*/
86+
get isEmptyOrWhitespace(): boolean {
87+
return this.firstNonWhitespaceCharacterIndex === this.text.length;
88+
}
89+
}
90+
91+
// map of file (asa strings) to language ids
92+
// TODO: is there a canonical list of these somewhere else?
93+
const languageIdMap: { [key: string]: string } = {
94+
txt: "plaintext",
95+
js: "javascript",
96+
ts: "typescript",
97+
go: "go",
98+
py: "python",
99+
rs: "rust",
100+
java: "java",
101+
c: "c",
102+
};
103+
104+
export class InMemoryTextDocument implements TextDocument {
105+
readonly uri: URI;
106+
readonly languageId: string;
107+
readonly version: number;
108+
readonly range: Range;
109+
readonly eol: EndOfLine;
110+
private lines: InMemoryTextLine[];
111+
private contents: string;
112+
113+
constructor(filename: string, contents: string) {
114+
this.uri = URI.file(filename);
115+
const extension = filename.slice(filename.lastIndexOf("."));
116+
this.languageId = languageIdMap[extension];
117+
this.version = 1;
118+
if (contents.indexOf("\r\n") !== -1) {
119+
throw new Error("InMemoryTextDocument does not support CRLF (yet?)");
120+
}
121+
this.contents = contents;
122+
const rawLines = contents.split("\n");
123+
this.lines = rawLines.map((line, i) => {
124+
return new InMemoryTextLine(i, line, "\n");
125+
});
126+
this.range = new Range(
127+
0,
128+
0,
129+
this.lineCount - 1,
130+
this.lineLength(this.lineCount - 1),
131+
);
132+
this.eol = "LF";
133+
}
134+
135+
get lineCount(): number {
136+
return this.lines.length;
137+
}
138+
139+
private lineLength(line: number): number {
140+
return this.lines[line].text.length + 1; // EOF
141+
}
142+
143+
public lineAt(x: number | Position): TextLine {
144+
if (typeof x === "number") {
145+
return this.lines[x];
146+
}
147+
return this.lines[x.line];
148+
}
149+
150+
public offsetAt(position: Position): number {
151+
// nice docs, vscode. what even is an offset??
152+
// "Converts the position to a zero-based offset."
153+
// thanks, that's very illuminating.
154+
// maybe this is right? i think?
155+
let offset = 0;
156+
for (let i = 0; i < position.line; i++) {
157+
offset += this.lineAt(i).rangeIncludingLineBreak.end.character;
158+
}
159+
offset += position.character;
160+
return offset;
161+
}
162+
163+
public positionAt(offset: number): Position {
164+
// "Converts a zero-based offset to a position."
165+
// uh, ok.
166+
// TODO: anyone's guess as to whether this is right
167+
let line = 0;
168+
// TODO: I had this as > originally
169+
// and things broke--we were getting bad ranges.
170+
// figure out how to write a unit test for it?
171+
while (offset >= this.lineAt(line).rangeIncludingLineBreak.end.character) {
172+
offset -= this.lineAt(line).rangeIncludingLineBreak.end.character;
173+
line++;
174+
}
175+
return new Position(line, offset);
176+
}
177+
178+
public getText(range?: Range): string {
179+
if (range === undefined) {
180+
return this.contents;
181+
}
182+
const startOffset = this.offsetAt(range.start);
183+
const endOffset = this.offsetAt(range.end);
184+
return this.contents.slice(startOffset, endOffset);
185+
}
186+
187+
public normalizePosition(position: Position): Position {
188+
return this.positionAt(this.offsetAt(position));
189+
}
190+
191+
public normalizeRange(range: Range): Range {
192+
return new Range(
193+
this.normalizePosition(range.start),
194+
this.normalizePosition(range.end),
195+
);
196+
}
197+
}
198+
199+
export class InMemoryTextEditor implements TextEditor {
200+
public primarySelection: Selection;
201+
202+
constructor(document: TextDocument, active: boolean) {
203+
this.id = document.uri.toString();
204+
this.document = document;
205+
this.primarySelection = new Selection(0, 0, 0, 0);
206+
// TODO: support visible ranges
207+
// TODO: support multiple selections
208+
// TODO: support options
209+
this.options = new DefaultTextEditorOptions();
210+
this.isActive = active;
211+
}
212+
213+
/**
214+
* Unique identifier for this text editor
215+
*/
216+
readonly id: string;
217+
218+
/**
219+
* The document associated with this text editor. The document will be the same for the entire lifetime of this text editor.
220+
*/
221+
readonly document: TextDocument;
222+
223+
/**
224+
* The current visible ranges in the editor (vertically).
225+
* This accounts only for vertical scrolling, and not for horizontal scrolling.
226+
*/
227+
get visibleRanges(): Range[] {
228+
return [this.document.range];
229+
}
230+
231+
/**
232+
* The selections in this text editor.
233+
*/
234+
get selections(): Selection[] {
235+
return [this.primarySelection];
236+
}
237+
238+
/**
239+
* Text editor options.
240+
*/
241+
readonly options: TextEditorOptions;
242+
243+
/**
244+
* True if this text editor is active.
245+
*/
246+
readonly isActive: boolean;
247+
248+
/**
249+
* Check if this text editor is equal to `other`.
250+
*
251+
* @param other A text editor.
252+
* @return `true` if the this text editor is equal to `other`.
253+
*/
254+
isEqual(other: TextEditor): boolean {
255+
return this.id === other.id;
256+
}
257+
}
258+
259+
class DefaultTextEditorOptions implements TextEditorOptions {
260+
get tabSize(): number | string {
261+
return 4;
262+
}
263+
264+
get insertSpaces(): boolean | string {
265+
return true;
266+
}
267+
}

packages/common/src/types/Range.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ export class Range {
6464
return this.start.isEqual(this.end);
6565
}
6666

67+
/**
68+
* Check if this range is equal to `other`.
69+
*
70+
* @param other A range.
71+
* @return `true` if the start and end of the given range are equal to
72+
* the start and end of this range.
73+
*/
74+
public isEqual(other: Range): boolean {
75+
return this.start.isEqual(other.start) && this.end.isEqual(other.end);
76+
}
77+
6778
/**
6879
* `true` if `start.line` and `end.line` are equal.
6980
*/
@@ -146,4 +157,12 @@ export class Range {
146157
? new Selection(this.end, this.start)
147158
: new Selection(this.start, this.end);
148159
}
160+
161+
/**
162+
* Return a concise representation of the range
163+
* @returns concise representation
164+
**/
165+
public concise(): string {
166+
return `(${this.start.line}:${this.start.character}-${this.end.line}:${this.end.character})`;
167+
}
149168
}

0 commit comments

Comments
 (0)