@@ -7,6 +7,7 @@ import * as vscode from 'vscode'
7
7
import { getLogger } from './logger/logger'
8
8
import * as pathutils from './utilities/pathUtils'
9
9
import * as path from 'path'
10
+ import { isUntitledScheme , normalizeVSCodeUri } from './utilities/vsCodeUtils'
10
11
11
12
export interface WatchedItem < T > {
12
13
/**
@@ -22,7 +23,7 @@ export interface WatchedItem<T> {
22
23
/**
23
24
* WatchedFiles lets us index files in the current registry. It is used
24
25
* for CFN templates among other things. WatchedFiles holds a list of pairs of
25
- * the absolute path to the file along with a transform of it that is useful for
26
+ * the absolute path to the file or "untitled:" URI along with a transform of it that is useful for
26
27
* where it is used. For example, for templates, it parses the template and stores it.
27
28
*/
28
29
export abstract class WatchedFiles < T > implements vscode . Disposable {
@@ -33,10 +34,13 @@ export abstract class WatchedFiles<T> implements vscode.Disposable {
33
34
private readonly registryData : Map < string , T > = new Map < string , T > ( )
34
35
35
36
/**
36
- * Load in filesystem items, doing any parsing/validaton as required. If it fails, throws
37
- * @param path A string with the absolute path to the detected file
37
+ * Process any incoming URI/content, doing any parsing/validation as required.
38
+ * If the path does not point to a file on the local file system then contents should be defined.
39
+ * If it fails, throws
40
+ * @param path A uri with the absolute path to the detected file
38
41
*/
39
- protected abstract load ( path : string ) : Promise < T | undefined >
42
+ protected abstract process ( path : vscode . Uri , contents ?: string ) : Promise < T | undefined >
43
+
40
44
/**
41
45
* Name for logs
42
46
*/
@@ -93,6 +97,25 @@ export abstract class WatchedFiles<T> implements vscode.Disposable {
93
97
await this . rebuild ( )
94
98
}
95
99
100
+ /**
101
+ * Create a special watcher that operates only on untitled files.
102
+ * To "watch" the in-memory contents of an untitled:/ file we just subscribe to `onDidChangeTextDocument`
103
+ */
104
+ public async watchUntitledFiles ( ) {
105
+ this . disposables . push (
106
+ vscode . workspace . onDidChangeTextDocument ( ( event : vscode . TextDocumentChangeEvent ) => {
107
+ if ( isUntitledScheme ( event . document . uri ) ) {
108
+ this . addItemToRegistry ( event . document . uri , true , event . document . getText ( ) )
109
+ }
110
+ } ) ,
111
+ vscode . workspace . onDidCloseTextDocument ( ( event : vscode . TextDocument ) => {
112
+ if ( isUntitledScheme ( event . uri ) ) {
113
+ this . remove ( event . uri )
114
+ }
115
+ } )
116
+ )
117
+ }
118
+
96
119
/**
97
120
* Adds a regex pattern to ignore paths containing the pattern
98
121
*/
@@ -109,16 +132,16 @@ export abstract class WatchedFiles<T> implements vscode.Disposable {
109
132
* Adds an item to registry. Wipes any existing item in its place with new copy of the data
110
133
* @param uri vscode.Uri containing the item to load in
111
134
*/
112
- public async addItemToRegistry ( uri : vscode . Uri , quiet ?: boolean ) : Promise < void > {
135
+ public async addItemToRegistry ( uri : vscode . Uri , quiet ?: boolean , contents ?: string ) : Promise < void > {
113
136
const excluded = this . excludedFilePatterns . find ( pattern => uri . fsPath . match ( pattern ) )
114
137
if ( excluded ) {
115
138
getLogger ( ) . verbose ( `${ this . name } : excluding path (matches "${ excluded } "): ${ uri . fsPath } ` )
116
139
return
117
140
}
118
- const pathAsString = pathutils . normalize ( uri . fsPath )
119
- this . assertAbsolute ( pathAsString )
141
+ this . assertAbsolute ( uri )
142
+ const pathAsString = normalizeVSCodeUri ( uri )
120
143
try {
121
- const item = await this . load ( pathAsString )
144
+ const item = await this . process ( uri , contents )
122
145
if ( item ) {
123
146
this . registryData . set ( pathAsString , item )
124
147
} else {
@@ -135,12 +158,12 @@ export abstract class WatchedFiles<T> implements vscode.Disposable {
135
158
136
159
/**
137
160
* Get a specific item's data
161
+ * Untitled files must be referred to by their URI
138
162
* @param path Absolute path to item of interest or a vscode.Uri to the item
139
163
*/
140
164
public getRegisteredItem ( path : string | vscode . Uri ) : WatchedItem < T > | undefined {
141
165
// fsPath is needed for Windows, it's equivalent to path on mac/linux
142
- const absolutePath = typeof path === 'string' ? path : path . fsPath
143
- const normalizedPath = pathutils . normalize ( absolutePath )
166
+ const normalizedPath = typeof path === 'string' ? pathutils . normalize ( path ) : normalizeVSCodeUri ( path )
144
167
this . assertAbsolute ( normalizedPath )
145
168
const item = this . registryData . get ( normalizedPath )
146
169
if ( ! item ) {
@@ -176,7 +199,7 @@ export abstract class WatchedFiles<T> implements vscode.Disposable {
176
199
if ( typeof path === 'string' ) {
177
200
this . registryData . delete ( path )
178
201
} else {
179
- const pathAsString = pathutils . normalize ( path . fsPath )
202
+ const pathAsString = normalizeVSCodeUri ( path )
180
203
this . assertAbsolute ( pathAsString )
181
204
this . registryData . delete ( pathAsString )
182
205
}
@@ -240,16 +263,25 @@ export abstract class WatchedFiles<T> implements vscode.Disposable {
240
263
)
241
264
}
242
265
243
- private assertAbsolute ( p : string ) {
244
- if ( ! path . isAbsolute ( p ) ) {
245
- throw Error ( `FileRegistry: path is relative when it should be absolute: ${ p } ` )
266
+ /**
267
+ * Assert if the path is absolute.
268
+ * Untitled URIs are considered absolute
269
+ * @param p The path to verify
270
+ */
271
+ private assertAbsolute ( p : string | vscode . Uri ) {
272
+ const pathAsString = typeof p === 'string' ? p : p . fsPath
273
+ if (
274
+ ( typeof p === 'string' && ! path . isAbsolute ( pathAsString ) && ! pathAsString . startsWith ( 'untitled:' ) ) ||
275
+ ( typeof p !== 'string' && ! path . isAbsolute ( pathAsString ) && ! isUntitledScheme ( p ) )
276
+ ) {
277
+ throw new Error ( `FileRegistry: path is relative when it should be absolute: ${ pathAsString } ` )
246
278
}
247
279
}
248
280
}
249
281
250
282
export class NoopWatcher extends WatchedFiles < any > {
251
- protected async load ( path : string ) : Promise < any > {
252
- throw new Error ( `Attempted to add a file to the NoopWatcher: ${ path } ` )
283
+ protected async process ( uri : vscode . Uri ) : Promise < any > {
284
+ throw new Error ( `Attempted to add a file to the NoopWatcher: ${ uri . fsPath } ` )
253
285
}
254
286
protected name : string = 'NoOp'
255
287
}
0 commit comments