-
Notifications
You must be signed in to change notification settings - Fork 13
/
workspace.ts
238 lines (199 loc) · 6.76 KB
/
workspace.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vscode-languageserver-protocol';
import { URI, Utils } from 'vscode-uri';
import { defaultMarkdownFileExtension, LsConfiguration } from './config';
import { ITextDocument } from './types/textDocument';
import { ResourceMap } from './util/resourceMap';
/**
* Result of {@link IWorkspace.stat stating} a file.
*/
export interface FileStat {
/**
* True if the file is directory.
*/
readonly isDirectory: boolean;
}
/**
* Information about a parent markdown document that contains sub-documents.
*
* This could be a notebook document for example, where the `children` are the Markdown cells in the notebook.
*/
export interface ContainingDocumentContext {
/**
* Uri of the parent document.
*/
readonly uri: URI;
/**
* List of child markdown documents.
*/
readonly children: Iterable<{ readonly uri: URI }>;
}
/**
* Provide information about the contents of a workspace.
*/
export interface IWorkspace {
/**
* Get the root folders for this workspace.
*/
get workspaceFolders(): readonly URI[];
/**
* Fired when the content of a markdown document changes.
*/
readonly onDidChangeMarkdownDocument: Event<ITextDocument>;
/**
* Fired when a markdown document is first created.
*/
readonly onDidCreateMarkdownDocument: Event<ITextDocument>;
/**
* Fired when a markdown document is deleted.
*/
readonly onDidDeleteMarkdownDocument: Event<URI>;
/**
* Get complete list of markdown documents.
*
* This may include documents that have not been opened yet (for example, getAllMarkdownDocuments should
* return documents from disk even if they have not been opened yet in the editor)
*/
getAllMarkdownDocuments(): Promise<Iterable<ITextDocument>>;
/**
* Check if a document already exists in the workspace contents.
*/
hasMarkdownDocument(resource: URI): boolean;
/**
* Try to open a markdown document.
*
* This may either get the document from a cache or open it and add it to the cache.
*
* @returns The document, or `undefined` if the file could not be opened or was not a markdown file.
*/
openMarkdownDocument(resource: URI): Promise<ITextDocument | undefined>;
/**
* Get metadata about a file.
*
* @param resource URI to check. Does not have to be to a markdown file.
*
* @returns Metadata or `undefined` if the resource does not exist.
*/
stat(resource: URI): Promise<FileStat | undefined>;
/**
* List all files in a directory.
*
* @param resource URI of the directory to check. Does not have to be to a markdown file.
*
* @returns List of `[fileName, metadata]` tuples.
*/
readDirectory(resource: URI): Promise<Iterable<readonly [string, FileStat]>>;
/**
* Get the document that contains `resource` as a sub document.
*
* If `resource` is a notebook cell for example, this should return the parent notebook.
*
* @returns The parent document info or `undefined` if none.
*/
getContainingDocument?(resource: URI): ContainingDocumentContext | undefined;
}
/**
* Configures which events a {@link IFileSystemWatcher} fires.
*/
export interface FileWatcherOptions {
/** Ignore file creation events. */
readonly ignoreCreate?: boolean;
/** Ignore file change events. */
readonly ignoreChange?: boolean;
/** Ignore file delete events. */
readonly ignoreDelete?: boolean;
}
/**
* A workspace that also supports watching arbitrary files.
*/
export interface IWorkspaceWithWatching extends IWorkspace {
/**
* Start watching a given file.
*/
watchFile(path: URI, options: FileWatcherOptions): IFileSystemWatcher;
}
export function isWorkspaceWithFileWatching(workspace: IWorkspace): workspace is IWorkspaceWithWatching {
return 'watchFile' in workspace;
}
/**
* Watches a file for changes to it on the file system.
*/
export interface IFileSystemWatcher {
/**
* Dispose of the watcher. This should stop watching and clean up any associated resources.
*/
dispose(): void;
/** Fired when the file is created. */
readonly onDidCreate: Event<URI>;
/** Fired when the file is changed on the file system. */
readonly onDidChange: Event<URI>;
/** Fired when the file is deleted. */
readonly onDidDelete: Event<URI>;
}
export function getWorkspaceFolder(workspace: IWorkspace, docUri: URI): URI | undefined {
if (workspace.workspaceFolders.length === 0) {
return undefined;
}
// Find the longest match
const possibleWorkspaces = workspace.workspaceFolders
.filter(folder =>
folder.scheme === docUri.scheme
&& folder.authority === docUri.authority
&& (docUri.fsPath.startsWith(folder.fsPath + '/') || docUri.fsPath.startsWith(folder.fsPath + '\\')))
.sort((a, b) => b.fsPath.length - a.fsPath.length);
if (possibleWorkspaces.length) {
return possibleWorkspaces[0];
}
// Default to first workspace
// TODO: Does this make sense?
return workspace.workspaceFolders[0];
}
export async function openLinkToMarkdownFile(config: LsConfiguration, workspace: IWorkspace, resource: URI): Promise<ITextDocument | undefined> {
try {
const doc = await workspace.openMarkdownDocument(resource);
if (doc) {
return doc;
}
} catch {
// Noop
}
const dotMdResource = tryAppendMarkdownFileExtension(config, resource);
if (dotMdResource) {
return workspace.openMarkdownDocument(dotMdResource);
}
return undefined;
}
/**
* Check that a link to a file exists.
*
* @returns The resolved URI or `undefined` if the file does not exist.
*/
export async function statLinkToMarkdownFile(config: LsConfiguration, workspace: IWorkspace, linkUri: URI, out_statCache?: ResourceMap<{ readonly exists: boolean }>): Promise<URI | undefined> {
const exists = async (uri: URI): Promise<boolean> => {
const result = await workspace.stat(uri);
out_statCache?.set(uri, { exists: !!result });
return !!result;
};
if (await exists(linkUri)) {
return linkUri;
}
// We don't think the file exists. See if we need to append `.md`
const dotMdResource = tryAppendMarkdownFileExtension(config, linkUri);
if (dotMdResource && await exists(dotMdResource)) {
return dotMdResource;
}
return undefined;
}
export function tryAppendMarkdownFileExtension(config: LsConfiguration, linkUri: URI): URI | undefined {
const ext = Utils.extname(linkUri).toLowerCase().replace(/^\./, '');
if (config.markdownFileExtensions.includes(ext)) {
return linkUri;
}
if (ext === '' || !config.knownLinkedToFileExtensions.includes(ext)) {
return linkUri.with({ path: linkUri.path + '.' + (config.markdownFileExtensions[0] ?? defaultMarkdownFileExtension) });
}
return undefined;
}