1
1
import {
2
- CustomRegexScopeType ,
3
2
Disposable ,
4
- FileSystem ,
3
+ Disposer ,
5
4
ScopeType ,
6
- SimpleScopeTypeType ,
7
- SurroundingPairName ,
8
5
SurroundingPairScopeType ,
9
- isSimpleScopeType ,
10
6
simpleScopeTypeTypes ,
11
7
surroundingPairNames ,
12
8
} from "@cursorless/common" ;
13
9
import { pull } from "lodash" ;
14
- import { ScopeTypeInfo , ScopeTypeInfoEventCallback } from ".." ;
15
- import { Debouncer } from "../core/Debouncer" ;
16
10
import { homedir } from "os" ;
17
11
import * as path from "path" ;
12
+ import { ScopeTypeInfo , ScopeTypeInfoEventCallback } from ".." ;
13
+ import { CustomSpokenForms } from "../CustomSpokenForms" ;
14
+
15
+ import { SpokenFormGenerator } from "../generateSpokenForm" ;
18
16
import { scopeTypeToString } from "./scopeTypeToString" ;
19
- import {
20
- CustomRegexSpokenFormEntry ,
21
- PairedDelimiterSpokenFormEntry ,
22
- SimpleScopeTypeTypeSpokenFormEntry ,
23
- getSpokenFormEntries ,
24
- } from "./getSpokenFormEntries" ;
25
17
26
18
export const spokenFormsPath = path . join (
27
19
homedir ( ) ,
@@ -33,31 +25,20 @@ export const spokenFormsPath = path.join(
33
25
* Maintains a list of all scope types and notifies listeners when it changes.
34
26
*/
35
27
export class ScopeInfoProvider {
36
- private disposables : Disposable [ ] = [ ] ;
37
- private debouncer = new Debouncer ( ( ) => this . onChange ( ) , 250 ) ;
28
+ private disposer = new Disposer ( ) ;
38
29
private listeners : ScopeTypeInfoEventCallback [ ] = [ ] ;
39
- private simpleScopeTypeSpokenFormMap ?: Record < SimpleScopeTypeType , string [ ] > ;
40
- private pairedDelimiterSpokenFormMap ?: Record < SurroundingPairName , string [ ] > ;
41
- private customRegexSpokenFormMap ?: Record < string , string [ ] > ;
42
30
private scopeInfos ! : ScopeTypeInfo [ ] ;
43
31
44
- private constructor ( fileSystem : FileSystem ) {
45
- this . disposables . push (
46
- fileSystem . watch ( spokenFormsPath , this . debouncer . run ) ,
47
- this . debouncer ,
32
+ constructor (
33
+ private customSpokenForms : CustomSpokenForms ,
34
+ private spokenFormGenerator : SpokenFormGenerator ,
35
+ ) {
36
+ this . disposer . push (
37
+ customSpokenForms . onDidChangeCustomSpokenForms ( ( ) => this . onChange ( ) ) ,
48
38
) ;
49
39
50
40
this . onDidChangeScopeInfo = this . onDidChangeScopeInfo . bind ( this ) ;
51
- }
52
-
53
- static create ( fileSystem : FileSystem ) {
54
- const obj = new ScopeInfoProvider ( fileSystem ) ;
55
- obj . init ( ) ;
56
- return obj ;
57
- }
58
-
59
- private async init ( ) {
60
- await this . updateScopeTypeInfos ( ) ;
41
+ this . updateScopeTypeInfos ( ) ;
61
42
}
62
43
63
44
/**
@@ -69,7 +50,6 @@ export class ScopeInfoProvider {
69
50
* @returns A {@link Disposable} which will stop the callback from running
70
51
*/
71
52
onDidChangeScopeInfo ( callback : ScopeTypeInfoEventCallback ) : Disposable {
72
- this . updateScopeTypeInfos ( ) . then ( ( ) => callback ( this . getScopeTypeInfos ( ) ) ) ;
73
53
callback ( this . getScopeTypeInfos ( ) ) ;
74
54
75
55
this . listeners . push ( callback ) ;
@@ -82,76 +62,36 @@ export class ScopeInfoProvider {
82
62
}
83
63
84
64
private async onChange ( ) {
85
- await this . updateScopeTypeInfos ( ) ;
65
+ this . updateScopeTypeInfos ( ) ;
86
66
87
67
this . listeners . forEach ( ( listener ) => listener ( this . scopeInfos ) ) ;
88
68
}
89
69
90
- private async updateScopeTypeInfos ( ) : Promise < void > {
91
- const update = ( ) => {
92
- const scopeTypes : ScopeType [ ] = [
93
- ...simpleScopeTypeTypes
94
- // Ignore instance pseudo-scope for now
95
- // Skip "string" because we use surrounding pair for that
96
- . filter (
97
- ( scopeTypeType ) =>
98
- scopeTypeType !== "instance" && scopeTypeType !== "string" ,
99
- )
100
- . map ( ( scopeTypeType ) => ( {
101
- type : scopeTypeType ,
102
- } ) ) ,
103
-
104
- ...surroundingPairNames . map (
105
- ( surroundingPairName ) : SurroundingPairScopeType => ( {
106
- type : "surroundingPair" ,
107
- delimiter : surroundingPairName ,
108
- } ) ,
109
- ) ,
110
-
111
- ...( this . customRegexSpokenFormMap == null
112
- ? [ ]
113
- : Object . keys ( this . customRegexSpokenFormMap )
114
- ) . map (
115
- ( regex ) : CustomRegexScopeType => ( { type : "customRegex" , regex } ) ,
116
- ) ,
117
- ] ;
118
-
119
- this . scopeInfos = scopeTypes . map ( ( scopeType ) =>
120
- this . getScopeTypeInfo ( scopeType ) ,
121
- ) ;
122
- } ;
123
-
124
- update ( ) ;
125
-
126
- return this . updateSpokenFormMaps ( ) . then ( update ) ;
127
- }
128
-
129
- private async updateSpokenFormMaps ( ) {
130
- const entries = await getSpokenFormEntries ( ) ;
131
-
132
- this . simpleScopeTypeSpokenFormMap = Object . fromEntries (
133
- entries
134
- . filter (
135
- ( entry ) : entry is SimpleScopeTypeTypeSpokenFormEntry =>
136
- entry . type === "simpleScopeTypeType" ,
137
- )
138
- . map ( ( { id, spokenForms } ) => [ id , spokenForms ] as const ) ,
139
- ) ;
140
- this . customRegexSpokenFormMap = Object . fromEntries (
141
- entries
142
- . filter (
143
- ( entry ) : entry is CustomRegexSpokenFormEntry =>
144
- entry . type === "customRegex" ,
145
- )
146
- . map ( ( { id, spokenForms } ) => [ id , spokenForms ] as const ) ,
147
- ) ;
148
- this . pairedDelimiterSpokenFormMap = Object . fromEntries (
149
- entries
70
+ private updateScopeTypeInfos ( ) : void {
71
+ const scopeTypes : ScopeType [ ] = [
72
+ ...simpleScopeTypeTypes
73
+ // Ignore instance pseudo-scope for now
74
+ // Skip "string" because we use surrounding pair for that
150
75
. filter (
151
- ( entry ) : entry is PairedDelimiterSpokenFormEntry =>
152
- entry . type === "pairedDelimiter ",
76
+ ( scopeTypeType ) =>
77
+ scopeTypeType !== "instance" && scopeTypeType !== "string ",
153
78
)
154
- . map ( ( { id, spokenForms } ) => [ id , spokenForms ] as const ) ,
79
+ . map ( ( scopeTypeType ) => ( {
80
+ type : scopeTypeType ,
81
+ } ) ) ,
82
+
83
+ ...surroundingPairNames . map (
84
+ ( surroundingPairName ) : SurroundingPairScopeType => ( {
85
+ type : "surroundingPair" ,
86
+ delimiter : surroundingPairName ,
87
+ } ) ,
88
+ ) ,
89
+
90
+ ...this . customSpokenForms . getCustomRegexScopeTypes ( ) ,
91
+ ] ;
92
+
93
+ this . scopeInfos = scopeTypes . map ( ( scopeType ) =>
94
+ this . getScopeTypeInfo ( scopeType ) ,
155
95
) ;
156
96
}
157
97
@@ -162,38 +102,11 @@ export class ScopeInfoProvider {
162
102
getScopeTypeInfo ( scopeType : ScopeType ) : ScopeTypeInfo {
163
103
return {
164
104
scopeType,
165
- spokenForms : this . getSpokenForms ( scopeType ) ,
105
+ spokenForm : this . spokenFormGenerator . scopeType ( scopeType ) ,
166
106
humanReadableName : scopeTypeToString ( scopeType ) ,
167
107
isLanguageSpecific : isLanguageSpecific ( scopeType ) ,
168
108
} ;
169
109
}
170
-
171
- getSpokenForms ( scopeType : ScopeType ) : string [ ] | undefined {
172
- if ( isSimpleScopeType ( scopeType ) ) {
173
- return this . simpleScopeTypeSpokenFormMap ?. [ scopeType . type ] ;
174
- }
175
-
176
- if ( scopeType . type === "surroundingPair" ) {
177
- return this . pairedDelimiterSpokenFormMap ?. [ scopeType . delimiter ] ;
178
- }
179
-
180
- if ( scopeType . type === "customRegex" ) {
181
- return this . customRegexSpokenFormMap ?. [ scopeType . regex ] ;
182
- }
183
-
184
- return undefined ;
185
- }
186
-
187
- dispose ( ) : void {
188
- this . disposables . forEach ( ( { dispose } ) => {
189
- try {
190
- dispose ( ) ;
191
- } catch ( e ) {
192
- // do nothing; some of the VSCode disposables misbehave, and we don't
193
- // want that to prevent us from disposing the rest of the disposables
194
- }
195
- } ) ;
196
- }
197
110
}
198
111
199
112
/**
0 commit comments