Skip to content

Commit 75d7d2c

Browse files
authored
Update custom containers (#280)
* Update code to use Set for unique values * Update custom containers
1 parent 6e6c3b6 commit 75d7d2c

File tree

5 files changed

+207
-57
lines changed

5 files changed

+207
-57
lines changed

src/color.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { CancellationToken, Color, ColorInformation, ColorPresentation, Document
33
import { injectCustomTextmateTokens, TextMateRule } from "./decorator";
44
import { LiteralTokenType } from "./tokenizer/renpy-tokens";
55
import { tokenizeDocument } from "./tokenizer/tokenizer";
6-
import util = require("util");
6+
import { ValueEqualsSet } from "./utilities/hashset";
77

88
export class RenpyColorProvider implements DocumentColorProvider {
99
public provideDocumentColors(document: TextDocument, token: CancellationToken): Thenable<ColorInformation[]> {
@@ -116,19 +116,13 @@ export function injectCustomColorStyles(document: TextDocument) {
116116

117117
// TODO: Should probably make sure this constant is actually part of a tag, but for now this is fine.
118118
const colorTags = documentTokens.filter((x) => x.tokenType === LiteralTokenType.Color);
119-
const colorRules: TextMateRule[] = [];
119+
const colorRules = new ValueEqualsSet<TextMateRule>();
120120

121121
// Build the new rules for this file
122122
colorTags.forEach((color) => {
123123
const lowerColor = document.getText(color.getRange()).toLowerCase();
124-
const newRule = {
125-
scope: `renpy.meta.color.${lowerColor}`,
126-
settings: { foreground: lowerColor },
127-
};
128-
129-
if (!colorRules.some((x) => util.isDeepStrictEqual(x, newRule))) {
130-
colorRules.push(newRule);
131-
}
124+
const newRule = new TextMateRule(`renpy.meta.color.${lowerColor}`, { foreground: lowerColor });
125+
colorRules.add(newRule);
132126
});
133127

134128
injectCustomTextmateTokens(colorRules);

src/decorator.ts

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,66 @@
11
import { ConfigurationTarget, workspace } from "vscode";
2+
import util = require("util");
3+
import { IEquatable, ValueEqualsSet } from "./utilities/hashset";
24

3-
export type TextMateRule = { scope: string | string[]; settings: { fontStyle?: string; foreground?: string } };
4-
type TextMateRules = { textMateRules: TextMateRule[] };
5+
export class TextMateRule implements IEquatable<TextMateRule> {
6+
public scope: string | string[];
7+
public settings: { fontStyle?: string; foreground?: string };
58

6-
const customFontStyleRules: TextMateRule[] = [
7-
{ scope: "renpy.meta.plain", settings: { fontStyle: "" } },
8-
{ scope: "renpy.meta.i", settings: { fontStyle: "italic" } },
9-
{ scope: "renpy.meta.b", settings: { fontStyle: "bold" } },
10-
{ scope: ["renpy.meta.u", "renpy.meta.a"], settings: { fontStyle: "underline" } },
11-
{ scope: "renpy.meta.s", settings: { fontStyle: "strikethrough" } },
9+
constructor(scope: string | string[], settings: { fontStyle?: string; foreground?: string }) {
10+
this.scope = scope;
11+
this.settings = settings;
12+
}
13+
14+
equals(other: TextMateRule): boolean {
15+
return util.isDeepStrictEqual(this, other);
16+
}
17+
}
18+
type TextMateRuleConfiguration = { scope: string | string[]; settings: { fontStyle?: string; foreground?: string } };
19+
type TextMateRules = { textMateRules: TextMateRuleConfiguration[] };
20+
21+
const customFontStyleRules = new ValueEqualsSet<TextMateRule>([
22+
new TextMateRule("renpy.meta.plain", { fontStyle: "" }),
23+
new TextMateRule("renpy.meta.i", { fontStyle: "italic" }),
24+
new TextMateRule("renpy.meta.b", { fontStyle: "bold" }),
25+
new TextMateRule(["renpy.meta.u", "renpy.meta.a"], { fontStyle: "underline" }),
26+
new TextMateRule("renpy.meta.s", { fontStyle: "strikethrough" }),
1227

13-
{ scope: "renpy.meta.i renpy.meta.b", settings: { fontStyle: "italic bold" } },
14-
{ scope: "renpy.meta.i renpy.meta.u", settings: { fontStyle: "italic underline" } },
15-
{ scope: "renpy.meta.i renpy.meta.s", settings: { fontStyle: "italic strikethrough" } },
16-
{ scope: "renpy.meta.b renpy.meta.u", settings: { fontStyle: "bold underline" } },
17-
{ scope: "renpy.meta.b renpy.meta.s", settings: { fontStyle: "bold strikethrough" } },
18-
{ scope: "renpy.meta.u renpy.meta.s", settings: { fontStyle: "underline strikethrough" } },
28+
new TextMateRule("renpy.meta.i renpy.meta.b", { fontStyle: "italic bold" }),
29+
new TextMateRule("renpy.meta.i renpy.meta.u", { fontStyle: "italic underline" }),
30+
new TextMateRule("renpy.meta.i renpy.meta.s", { fontStyle: "italic strikethrough" }),
31+
new TextMateRule("renpy.meta.b renpy.meta.u", { fontStyle: "bold underline" }),
32+
new TextMateRule("renpy.meta.b renpy.meta.s", { fontStyle: "bold strikethrough" }),
33+
new TextMateRule("renpy.meta.u renpy.meta.s", { fontStyle: "underline strikethrough" }),
1934

20-
{ scope: "renpy.meta.i renpy.meta.b renpy.meta.u", settings: { fontStyle: "italic bold underline" } },
21-
{ scope: "renpy.meta.i renpy.meta.b renpy.meta.s", settings: { fontStyle: "italic bold strikethrough" } },
22-
{ scope: "renpy.meta.i renpy.meta.u renpy.meta.s", settings: { fontStyle: "italic underline strikethrough" } },
23-
{ scope: "renpy.meta.b renpy.meta.u renpy.meta.s", settings: { fontStyle: "bold underline strikethrough" } },
35+
new TextMateRule("renpy.meta.i renpy.meta.b renpy.meta.u", { fontStyle: "italic bold underline" }),
36+
new TextMateRule("renpy.meta.i renpy.meta.b renpy.meta.s", { fontStyle: "italic bold strikethrough" }),
37+
new TextMateRule("renpy.meta.i renpy.meta.u renpy.meta.s", { fontStyle: "italic underline strikethrough" }),
38+
new TextMateRule("renpy.meta.b renpy.meta.u renpy.meta.s", { fontStyle: "bold underline strikethrough" }),
2439

25-
{ scope: "renpy.meta.i renpy.meta.b renpy.meta.u renpy.meta.s", settings: { fontStyle: "italic bold underline strikethrough" } },
40+
new TextMateRule("renpy.meta.i renpy.meta.b renpy.meta.u renpy.meta.s", { fontStyle: "italic bold underline strikethrough" }),
2641

27-
{ scope: "renpy.meta.color.text", settings: { foreground: "#ffffff" } },
28-
];
42+
new TextMateRule("renpy.meta.color.text", { foreground: "#ffffff" }),
43+
]);
2944

30-
export function injectCustomTextmateTokens(rules: TextMateRule[]) {
45+
export function injectCustomTextmateTokens(rules: ValueEqualsSet<TextMateRule>) {
3146
const tokensConfig = workspace.getConfiguration("editor");
3247

3348
// If the config didn't exist yet, push the default tokens
3449
let tokenColorCustomizations = tokensConfig.get<TextMateRules>("tokenColorCustomizations");
35-
if (tokenColorCustomizations === undefined || tokenColorCustomizations.textMateRules === undefined) tokenColorCustomizations = { textMateRules: customFontStyleRules };
50+
if (tokenColorCustomizations === undefined || tokenColorCustomizations.textMateRules === undefined) {
51+
tokenColorCustomizations = { textMateRules: customFontStyleRules.toArray() };
52+
}
3653

3754
const currentRules = tokenColorCustomizations.textMateRules;
3855

3956
// Build the new rules for this file
40-
const newRules = customFontStyleRules.concat(rules); // Always add the default rules
41-
const filteredRules = newRules.filter((y) => {
42-
return !currentRules.some((x) => {
43-
if (x.scope instanceof Array && y.scope instanceof Array) {
44-
return x.scope.length === y.scope.length && x.scope.every((value, index) => value === y.scope[index]);
45-
}
46-
47-
return x.scope === y.scope || (x.scope === y.scope && (x.settings.foreground !== y.settings.foreground || x.settings.fontStyle !== y.settings.fontStyle));
48-
});
49-
});
57+
const newRules = customFontStyleRules.addRange(rules); // Always add the default rules
58+
for (const rule of currentRules) {
59+
newRules.add(new TextMateRule(rule.scope, rule.settings));
60+
}
5061

51-
if (filteredRules.length !== 0) {
52-
tokenColorCustomizations.textMateRules = currentRules.concat(filteredRules);
62+
if (newRules.size !== 0) {
63+
tokenColorCustomizations.textMateRules = newRules.toArray();
5364
tokensConfig.update("tokenColorCustomizations", tokenColorCustomizations, ConfigurationTarget.Workspace).then(
5465
() => {
5566
console.log("Successfully updated the tokenColorCustomizations config");

src/utilities/hashset.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
export interface IEquatable<T> {
2+
/**
3+
* Returns `true` if the two objects are equal, `false` otherwise.
4+
*/
5+
equals(object: T): boolean;
6+
}
7+
8+
/**
9+
* A set that only allows unique values based on the `equals` method.
10+
* @param T The type of the values in the set
11+
* @remarks This is a workaround for the fact that `Set` doesn't allow custom equality checks.
12+
* The current implementation is not very efficient, but it works.
13+
*/
14+
export class ValueEqualsSet<T extends IEquatable<T>> extends Set<T> {
15+
add(value: T) {
16+
if (!this.has(value)) {
17+
super.add(value);
18+
}
19+
return this;
20+
}
21+
22+
addRange(values: Iterable<T>) {
23+
for (const value of values) {
24+
this.add(value);
25+
}
26+
return this;
27+
}
28+
29+
has(otherValue: T): boolean {
30+
for (const value of this.values()) {
31+
if (value.equals(otherValue)) {
32+
return true;
33+
}
34+
}
35+
return false;
36+
}
37+
38+
toArray(): T[] {
39+
return Array.from(this);
40+
}
41+
}
42+
43+
/**
44+
* An optimized hash set implementation which uses a hashing function and buckets to store values.
45+
* @remarks This is a work in progress, and has not been tested yet.
46+
*/
47+
export class HashSet<T> implements Iterable<T> {
48+
private _buckets: T[][] = [];
49+
private _size = 0;
50+
51+
constructor(private readonly _hash: (value: T) => number) {}
52+
53+
get size() {
54+
return this._size;
55+
}
56+
57+
add(value: T) {
58+
const hash = this._hash(value);
59+
const bucket = this._buckets[hash];
60+
if (bucket === undefined) {
61+
this._buckets[hash] = [value];
62+
} else {
63+
if (!bucket.includes(value)) {
64+
bucket.push(value);
65+
}
66+
}
67+
this._size++;
68+
}
69+
70+
addRange(values: Iterable<T>) {
71+
for (const value of values) {
72+
this.add(value);
73+
}
74+
}
75+
76+
has(value: T) {
77+
const hash = this._hash(value);
78+
const bucket = this._buckets[hash];
79+
return bucket !== undefined && bucket.includes(value);
80+
}
81+
82+
remove(value: T) {
83+
const hash = this._hash(value);
84+
const bucket = this._buckets[hash];
85+
if (bucket !== undefined) {
86+
const index = bucket.indexOf(value);
87+
if (index >= 0) {
88+
bucket.splice(index, 1);
89+
this._size--;
90+
}
91+
}
92+
}
93+
94+
[Symbol.iterator](): Iterator<T> {
95+
let index = 0;
96+
let bucketIndex = 0;
97+
let bucket: T[] | undefined = this._buckets[0];
98+
return {
99+
next: () => {
100+
while (bucket === undefined || index >= bucket.length) {
101+
bucket = this._buckets[++bucketIndex];
102+
index = 0;
103+
}
104+
return { value: bucket[index++], done: false };
105+
},
106+
};
107+
}
108+
109+
toArray(): T[] {
110+
const result: T[] = [];
111+
for (const bucket of this._buckets) {
112+
if (bucket !== undefined) {
113+
result.push(...bucket);
114+
}
115+
}
116+
return result;
117+
}
118+
}

src/utilities/stack.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export class Stack<T> {
1+
export class Stack<T> implements Iterable<T> {
22
private buffer: Array<T | null> = [];
33

44
private headPtr = -1;
@@ -14,12 +14,24 @@ export class Stack<T> {
1414
if (capacity > 0) this.buffer = new Array<T | null>(capacity).fill(null);
1515
}
1616

17+
[Symbol.iterator](): Iterator<T> {
18+
let index = 0;
19+
return {
20+
next: () => {
21+
if (index >= this.size) {
22+
return { done: true, value: null };
23+
}
24+
return { done: false, value: this.buffer[index++] as T };
25+
},
26+
};
27+
}
28+
1729
/**
1830
* Add a new item on top of the stack
1931
* @param item The item to add
2032
*/
2133
public push(item: T) {
22-
if (this.size() === this.capacity()) this._grow();
34+
if (this.size === this.capacity) this._grow();
2335

2436
++this.itemCount;
2537
++this.headPtr;
@@ -62,15 +74,15 @@ export class Stack<T> {
6274
* Get the amount of items that the stack is able to hold at this time
6375
* @returns The amount items that could fit in the internal memory buffer
6476
*/
65-
public capacity() {
77+
get capacity() {
6678
return this.buffer.length;
6779
}
6880

6981
/**
7082
* Get the amount of items that are on the stack
7183
* @returns The number of items
7284
*/
73-
public size() {
85+
get size() {
7486
return this.itemCount;
7587
}
7688

@@ -102,14 +114,14 @@ export class Stack<T> {
102114
* Shrink the internal memory buffer to match the size of the stack
103115
*/
104116
public shrink() {
105-
this.buffer.length = this.size();
117+
this.buffer.length = this.size;
106118
}
107119

108120
/**
109121
* Grow the buffer to double the current capacity
110122
*/
111123
private _grow() {
112-
const currentCapacity = Math.max(this.capacity(), 1);
124+
const currentCapacity = Math.max(this.capacity, 1);
113125

114126
this.buffer = this.buffer.concat(new Array<T | null>(currentCapacity).fill(null));
115127
}

src/utilities/vector.ts

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
export class Vector<T> {
1+
/**
2+
* A dynamically sized array that can be used to store items of any type.
3+
*/
4+
export class Vector<T> implements Iterable<T> {
25
private buffer: Array<T | null> = [];
36

47
private headPtr = -1;
@@ -15,12 +18,24 @@ export class Vector<T> {
1518
if (capacity > 0) this.buffer = new Array<T | null>(capacity).fill(null);
1619
}
1720

21+
[Symbol.iterator](): Iterator<T> {
22+
let index = 0;
23+
return {
24+
next: () => {
25+
if (index >= this.size) {
26+
return { done: true, value: null };
27+
}
28+
return { done: false, value: this.buffer[index++] as T };
29+
},
30+
};
31+
}
32+
1833
/**
1934
* Add a new item to the end of the vector
2035
* @param item The item to add
2136
*/
2237
public pushBack(item: T) {
23-
if (this.size() === this.capacity()) {
38+
if (this.size === this.capacity) {
2439
this._grow();
2540
}
2641

@@ -149,15 +164,15 @@ export class Vector<T> {
149164
* Get the amount of items that the vector is able to hold at this time
150165
* @returns The amount items that could fit in the internal memory buffer
151166
*/
152-
public capacity() {
167+
get capacity() {
153168
return this.buffer.length;
154169
}
155170

156171
/**
157172
* Get the amount of items that are on the vector
158173
* @returns The number of items
159174
*/
160-
public size() {
175+
get size() {
161176
return this.itemCount;
162177
}
163178

@@ -199,14 +214,14 @@ export class Vector<T> {
199214
* Shrink the internal memory buffer to match the size of the vector
200215
*/
201216
public shrink() {
202-
this.buffer.length = this.size();
217+
this.buffer.length = this.size;
203218
}
204219

205220
/**
206221
* Grow the buffer to double the current capacity
207222
*/
208223
private _grow() {
209-
const currentCapacity = Math.max(this.capacity(), 1);
224+
const currentCapacity = Math.max(this.capacity, 1);
210225

211226
this.buffer = this.buffer.concat(new Array<T | null>(currentCapacity).fill(null));
212227
}

0 commit comments

Comments
 (0)