@theia/preview 1.45.1 → 1.46.0-next.72

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +45 -45
  2. package/lib/browser/index.d.ts +3 -3
  3. package/lib/browser/index.js +29 -29
  4. package/lib/browser/markdown/index.d.ts +1 -1
  5. package/lib/browser/markdown/index.js +28 -28
  6. package/lib/browser/markdown/markdown-preview-handler.d.ts +28 -28
  7. package/lib/browser/markdown/markdown-preview-handler.js +301 -301
  8. package/lib/browser/markdown/markdown-preview-handler.spec.d.ts +1 -1
  9. package/lib/browser/markdown/markdown-preview-handler.spec.js +193 -193
  10. package/lib/browser/preview-contribution.d.ts +50 -50
  11. package/lib/browser/preview-contribution.js +262 -262
  12. package/lib/browser/preview-frontend-module.d.ts +5 -5
  13. package/lib/browser/preview-frontend-module.js +52 -52
  14. package/lib/browser/preview-handler.d.ts +104 -104
  15. package/lib/browser/preview-handler.js +76 -76
  16. package/lib/browser/preview-link-normalizer.d.ts +7 -7
  17. package/lib/browser/preview-link-normalizer.js +54 -54
  18. package/lib/browser/preview-preferences.d.ts +11 -11
  19. package/lib/browser/preview-preferences.js +46 -46
  20. package/lib/browser/preview-uri.d.ts +8 -8
  21. package/lib/browser/preview-uri.js +47 -47
  22. package/lib/browser/preview-widget.d.ts +54 -54
  23. package/lib/browser/preview-widget.js +261 -261
  24. package/lib/package.spec.js +25 -25
  25. package/package.json +7 -7
  26. package/src/browser/index.ts +19 -19
  27. package/src/browser/markdown/index.ts +17 -17
  28. package/src/browser/markdown/markdown-preview-handler.spec.ts +228 -228
  29. package/src/browser/markdown/markdown-preview-handler.ts +309 -309
  30. package/src/browser/markdown/style/index.css +18 -18
  31. package/src/browser/markdown/style/markdown.css +203 -203
  32. package/src/browser/markdown/style/tomorrow.css +105 -105
  33. package/src/browser/preview-contribution.ts +276 -276
  34. package/src/browser/preview-frontend-module.ts +57 -57
  35. package/src/browser/preview-handler.ts +141 -141
  36. package/src/browser/preview-link-normalizer.ts +40 -40
  37. package/src/browser/preview-preferences.ts +58 -58
  38. package/src/browser/preview-uri.ts +43 -43
  39. package/src/browser/preview-widget.ts +277 -277
  40. package/src/browser/style/index.css +17 -17
  41. package/src/browser/style/preview-widget.css +29 -29
  42. package/src/package.spec.ts +29 -29
@@ -1,43 +1,43 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2018 TypeFox and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
-
17
- import URI from '@theia/core/lib/common/uri';
18
-
19
- export namespace PreviewUri {
20
- export const id = 'code-editor-preview';
21
- export const param = 'open-handler=' + id;
22
- export function match(uri: URI): boolean {
23
- return uri.query.indexOf(param) !== -1;
24
- }
25
- export function encode(uri: URI): URI {
26
- if (match(uri)) {
27
- return uri;
28
- }
29
- const params = [param];
30
- if (uri.query) {
31
- params.push(...uri.query.split('&'));
32
- }
33
- const query = params.join('&');
34
- return uri.withQuery(query);
35
- }
36
- export function decode(uri: URI): URI {
37
- if (!match(uri)) {
38
- return uri;
39
- }
40
- const query = uri.query.split('&').filter(p => p !== param).join('&');
41
- return uri.withQuery(query);
42
- }
43
- }
1
+ // *****************************************************************************
2
+ // Copyright (C) 2018 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import URI from '@theia/core/lib/common/uri';
18
+
19
+ export namespace PreviewUri {
20
+ export const id = 'code-editor-preview';
21
+ export const param = 'open-handler=' + id;
22
+ export function match(uri: URI): boolean {
23
+ return uri.query.indexOf(param) !== -1;
24
+ }
25
+ export function encode(uri: URI): URI {
26
+ if (match(uri)) {
27
+ return uri;
28
+ }
29
+ const params = [param];
30
+ if (uri.query) {
31
+ params.push(...uri.query.split('&'));
32
+ }
33
+ const query = params.join('&');
34
+ return uri.withQuery(query);
35
+ }
36
+ export function decode(uri: URI): URI {
37
+ if (!match(uri)) {
38
+ return uri;
39
+ }
40
+ const query = uri.query.split('&').filter(p => p !== param).join('&');
41
+ return uri.withQuery(query);
42
+ }
43
+ }
@@ -1,277 +1,277 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2018 TypeFox and others.
3
- //
4
- // This program and the accompanying materials are made available under the
5
- // terms of the Eclipse Public License v. 2.0 which is available at
6
- // http://www.eclipse.org/legal/epl-2.0.
7
- //
8
- // This Source Code may also be made available under the following Secondary
9
- // Licenses when the conditions for such availability set forth in the Eclipse
10
- // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
- // with the GNU Classpath Exception which is available at
12
- // https://www.gnu.org/software/classpath/license.html.
13
- //
14
- // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
- // *****************************************************************************
16
-
17
- import throttle = require('@theia/core/shared/lodash.throttle');
18
- import { inject, injectable } from '@theia/core/shared/inversify';
19
- import { Resource, MaybePromise } from '@theia/core';
20
- import { Navigatable } from '@theia/core/lib/browser/navigatable';
21
- import { BaseWidget, Message, addEventListener, codicon } from '@theia/core/lib/browser';
22
- import URI from '@theia/core/lib/common/uri';
23
- import { Event, Emitter } from '@theia/core/lib/common';
24
- import { PreviewHandler, PreviewHandlerProvider } from './preview-handler';
25
- import { ThemeService } from '@theia/core/lib/browser/theming';
26
- import { EditorPreferences } from '@theia/editor/lib/browser';
27
- import { Disposable } from '@theia/core/lib/common/disposable';
28
- import { MonacoWorkspace } from '@theia/monaco/lib/browser/monaco-workspace';
29
- import { Range, Location } from '@theia/core/shared/vscode-languageserver-protocol';
30
-
31
- export const PREVIEW_WIDGET_CLASS = 'theia-preview-widget';
32
-
33
- const DEFAULT_ICON = codicon('eye');
34
-
35
- let widgetCounter: number = 0;
36
-
37
- export const PreviewWidgetOptions = Symbol('PreviewWidgetOptions');
38
- export interface PreviewWidgetOptions {
39
- resource: Resource
40
- }
41
-
42
- @injectable()
43
- export class PreviewWidget extends BaseWidget implements Navigatable {
44
-
45
- readonly uri: URI;
46
- protected readonly resource: Resource;
47
- protected previewHandler: PreviewHandler | undefined;
48
- protected firstUpdate: (() => void) | undefined = undefined;
49
- protected readonly onDidScrollEmitter = new Emitter<number>();
50
- protected readonly onDidDoubleClickEmitter = new Emitter<Location>();
51
- protected scrollBeyondLastLine: boolean;
52
-
53
- constructor(
54
- @inject(PreviewWidgetOptions) protected readonly options: PreviewWidgetOptions,
55
- @inject(PreviewHandlerProvider) protected readonly previewHandlerProvider: PreviewHandlerProvider,
56
- @inject(ThemeService) protected readonly themeService: ThemeService,
57
- @inject(MonacoWorkspace) protected readonly workspace: MonacoWorkspace,
58
- @inject(EditorPreferences) protected readonly editorPreferences: EditorPreferences,
59
- ) {
60
- super();
61
- this.resource = this.options.resource;
62
- this.uri = this.resource.uri;
63
- this.id = 'preview-widget-' + widgetCounter++;
64
- this.title.closable = true;
65
- this.title.label = `Preview ${this.uri.path.base}`;
66
- this.title.caption = this.title.label;
67
- this.title.closable = true;
68
-
69
- this.toDispose.push(this.onDidScrollEmitter);
70
- this.toDispose.push(this.onDidDoubleClickEmitter);
71
-
72
- this.addClass(PREVIEW_WIDGET_CLASS);
73
- this.node.tabIndex = 0;
74
- const previewHandler = this.previewHandler = this.previewHandlerProvider.findContribution(this.uri)[0];
75
- if (!previewHandler) {
76
- return;
77
- }
78
- this.title.iconClass = previewHandler.iconClass || DEFAULT_ICON;
79
- this.initialize();
80
- }
81
-
82
- async initialize(): Promise<void> {
83
- this.scrollBeyondLastLine = !!this.editorPreferences['editor.scrollBeyondLastLine'];
84
- this.toDispose.push(this.editorPreferences.onPreferenceChanged(e => {
85
- if (e.preferenceName === 'editor.scrollBeyondLastLine') {
86
- this.scrollBeyondLastLine = Boolean(e.newValue);
87
- this.forceUpdate();
88
- }
89
- }));
90
- this.toDispose.push(this.resource);
91
- if (this.resource.onDidChangeContents) {
92
- this.toDispose.push(this.resource.onDidChangeContents(() => this.update()));
93
- }
94
- const updateIfAffected = (affectedUri?: string) => {
95
- if (!affectedUri || affectedUri === this.uri.toString()) {
96
- this.update();
97
- }
98
- };
99
- this.toDispose.push(this.workspace.onDidOpenTextDocument(document => updateIfAffected(document.uri)));
100
- this.toDispose.push(this.workspace.onDidChangeTextDocument(params => updateIfAffected(params.model.uri)));
101
- this.toDispose.push(this.workspace.onDidCloseTextDocument(document => updateIfAffected(document.uri)));
102
- this.toDispose.push(this.themeService.onDidColorThemeChange(() => this.update()));
103
- this.firstUpdate = () => {
104
- this.revealFragment(this.uri);
105
- };
106
- this.update();
107
- }
108
-
109
- protected override onBeforeAttach(msg: Message): void {
110
- super.onBeforeAttach(msg);
111
- this.toDispose.push(this.startScrollSync());
112
- this.toDispose.push(this.startDoubleClickListener());
113
- }
114
-
115
- protected preventScrollNotification: boolean = false;
116
- protected startScrollSync(): Disposable {
117
- return addEventListener(this.node, 'scroll', throttle((event: UIEvent) => {
118
- if (this.preventScrollNotification) {
119
- return;
120
- }
121
- const scrollTop = this.node.scrollTop;
122
- this.didScroll(scrollTop);
123
- }, 50));
124
- }
125
-
126
- protected startDoubleClickListener(): Disposable {
127
- return addEventListener(this.node, 'dblclick', (event: MouseEvent) => {
128
- if (!(event.target instanceof HTMLElement)) {
129
- return;
130
- }
131
- const target = event.target as HTMLElement;
132
- let node: HTMLElement | null = target;
133
- while (node && node instanceof HTMLElement) {
134
- if (node.tagName === 'A') {
135
- return;
136
- }
137
- node = node.parentElement;
138
- }
139
- const offsetParent = target.offsetParent as HTMLElement;
140
- const offset = offsetParent.classList.contains(PREVIEW_WIDGET_CLASS) ? target.offsetTop : offsetParent.offsetTop;
141
- this.didDoubleClick(offset);
142
- });
143
- }
144
-
145
- getUri(): URI {
146
- return this.uri;
147
- }
148
-
149
- getResourceUri(): URI | undefined {
150
- return this.uri;
151
- }
152
- createMoveToUri(resourceUri: URI): URI | undefined {
153
- return this.uri.withPath(resourceUri.path);
154
- }
155
-
156
- override onActivateRequest(msg: Message): void {
157
- super.onActivateRequest(msg);
158
- this.node.focus();
159
- this.update();
160
- }
161
-
162
- override onUpdateRequest(msg: Message): void {
163
- super.onUpdateRequest(msg);
164
- this.performUpdate();
165
- }
166
-
167
- protected forceUpdate(): void {
168
- this.previousContent = undefined;
169
- this.update();
170
- }
171
-
172
- protected previousContent: string | undefined = undefined;
173
- protected async performUpdate(): Promise<void> {
174
- if (!this.resource) {
175
- return;
176
- }
177
- const uri = this.resource.uri;
178
- const document = this.workspace.textDocuments.find(d => d.uri === uri.toString());
179
- const content: MaybePromise<string> = document ? document.getText() : await this.resource.readContents();
180
- if (content === this.previousContent) {
181
- return;
182
- }
183
- this.previousContent = content;
184
- const contentElement = await this.render(content, uri);
185
- this.node.innerHTML = '';
186
- if (contentElement) {
187
- if (this.scrollBeyondLastLine) {
188
- contentElement.classList.add('scrollBeyondLastLine');
189
- }
190
- this.node.appendChild(contentElement);
191
- if (this.firstUpdate) {
192
- this.firstUpdate();
193
- this.firstUpdate = undefined;
194
- }
195
- }
196
- }
197
-
198
- protected async render(content: string, originUri: URI): Promise<HTMLElement | undefined> {
199
- if (!this.previewHandler || !this.resource) {
200
- return undefined;
201
- }
202
- return this.previewHandler.renderContent({ content, originUri });
203
- }
204
-
205
- protected revealFragment(uri: URI): void {
206
- if (uri.fragment === '' || !this.previewHandler || !this.previewHandler.findElementForFragment) {
207
- return;
208
- }
209
- const elementToReveal = this.previewHandler.findElementForFragment(this.node, uri.fragment);
210
- if (elementToReveal) {
211
- this.preventScrollNotification = true;
212
- elementToReveal.scrollIntoView();
213
- window.setTimeout(() => {
214
- this.preventScrollNotification = false;
215
- }, 50);
216
- }
217
- }
218
-
219
- revealForSourceLine(sourceLine: number): void {
220
- this.internalRevealForSourceLine(sourceLine);
221
- }
222
- protected readonly internalRevealForSourceLine: (sourceLine: number) => void = throttle((sourceLine: number) => {
223
- if (!this.previewHandler || !this.previewHandler.findElementForSourceLine) {
224
- return;
225
- }
226
- const elementToReveal = this.previewHandler.findElementForSourceLine(this.node, sourceLine);
227
- if (elementToReveal) {
228
- this.preventScrollNotification = true;
229
- elementToReveal.scrollIntoView();
230
- window.setTimeout(() => {
231
- this.preventScrollNotification = false;
232
- }, 50);
233
- }
234
- }, 50);
235
-
236
- get onDidScroll(): Event<number> {
237
- return this.onDidScrollEmitter.event;
238
- }
239
-
240
- protected fireDidScrollToSourceLine(line: number): void {
241
- this.onDidScrollEmitter.fire(line);
242
- }
243
-
244
- protected didScroll(scrollTop: number): void {
245
- if (!this.previewHandler || !this.previewHandler.getSourceLineForOffset) {
246
- return;
247
- }
248
- const offset = scrollTop;
249
- const line = this.previewHandler.getSourceLineForOffset(this.node, offset);
250
- if (line) {
251
- this.fireDidScrollToSourceLine(line);
252
- }
253
- }
254
-
255
- get onDidDoubleClick(): Event<Location> {
256
- return this.onDidDoubleClickEmitter.event;
257
- }
258
-
259
- protected fireDidDoubleClickToSourceLine(line: number): void {
260
- if (!this.resource) {
261
- return;
262
- }
263
- this.onDidDoubleClickEmitter.fire({
264
- uri: this.resource.uri.toString(),
265
- range: Range.create({ line, character: 0 }, { line, character: 0 })
266
- });
267
- }
268
-
269
- protected didDoubleClick(offsetTop: number): void {
270
- if (!this.previewHandler || !this.previewHandler.getSourceLineForOffset) {
271
- return;
272
- }
273
- const line = this.previewHandler.getSourceLineForOffset(this.node, offsetTop) || 0;
274
- this.fireDidDoubleClickToSourceLine(line);
275
- }
276
-
277
- }
1
+ // *****************************************************************************
2
+ // Copyright (C) 2018 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import throttle = require('@theia/core/shared/lodash.throttle');
18
+ import { inject, injectable } from '@theia/core/shared/inversify';
19
+ import { Resource, MaybePromise } from '@theia/core';
20
+ import { Navigatable } from '@theia/core/lib/browser/navigatable';
21
+ import { BaseWidget, Message, addEventListener, codicon } from '@theia/core/lib/browser';
22
+ import URI from '@theia/core/lib/common/uri';
23
+ import { Event, Emitter } from '@theia/core/lib/common';
24
+ import { PreviewHandler, PreviewHandlerProvider } from './preview-handler';
25
+ import { ThemeService } from '@theia/core/lib/browser/theming';
26
+ import { EditorPreferences } from '@theia/editor/lib/browser';
27
+ import { Disposable } from '@theia/core/lib/common/disposable';
28
+ import { MonacoWorkspace } from '@theia/monaco/lib/browser/monaco-workspace';
29
+ import { Range, Location } from '@theia/core/shared/vscode-languageserver-protocol';
30
+
31
+ export const PREVIEW_WIDGET_CLASS = 'theia-preview-widget';
32
+
33
+ const DEFAULT_ICON = codicon('eye');
34
+
35
+ let widgetCounter: number = 0;
36
+
37
+ export const PreviewWidgetOptions = Symbol('PreviewWidgetOptions');
38
+ export interface PreviewWidgetOptions {
39
+ resource: Resource
40
+ }
41
+
42
+ @injectable()
43
+ export class PreviewWidget extends BaseWidget implements Navigatable {
44
+
45
+ readonly uri: URI;
46
+ protected readonly resource: Resource;
47
+ protected previewHandler: PreviewHandler | undefined;
48
+ protected firstUpdate: (() => void) | undefined = undefined;
49
+ protected readonly onDidScrollEmitter = new Emitter<number>();
50
+ protected readonly onDidDoubleClickEmitter = new Emitter<Location>();
51
+ protected scrollBeyondLastLine: boolean;
52
+
53
+ constructor(
54
+ @inject(PreviewWidgetOptions) protected readonly options: PreviewWidgetOptions,
55
+ @inject(PreviewHandlerProvider) protected readonly previewHandlerProvider: PreviewHandlerProvider,
56
+ @inject(ThemeService) protected readonly themeService: ThemeService,
57
+ @inject(MonacoWorkspace) protected readonly workspace: MonacoWorkspace,
58
+ @inject(EditorPreferences) protected readonly editorPreferences: EditorPreferences,
59
+ ) {
60
+ super();
61
+ this.resource = this.options.resource;
62
+ this.uri = this.resource.uri;
63
+ this.id = 'preview-widget-' + widgetCounter++;
64
+ this.title.closable = true;
65
+ this.title.label = `Preview ${this.uri.path.base}`;
66
+ this.title.caption = this.title.label;
67
+ this.title.closable = true;
68
+
69
+ this.toDispose.push(this.onDidScrollEmitter);
70
+ this.toDispose.push(this.onDidDoubleClickEmitter);
71
+
72
+ this.addClass(PREVIEW_WIDGET_CLASS);
73
+ this.node.tabIndex = 0;
74
+ const previewHandler = this.previewHandler = this.previewHandlerProvider.findContribution(this.uri)[0];
75
+ if (!previewHandler) {
76
+ return;
77
+ }
78
+ this.title.iconClass = previewHandler.iconClass || DEFAULT_ICON;
79
+ this.initialize();
80
+ }
81
+
82
+ async initialize(): Promise<void> {
83
+ this.scrollBeyondLastLine = !!this.editorPreferences['editor.scrollBeyondLastLine'];
84
+ this.toDispose.push(this.editorPreferences.onPreferenceChanged(e => {
85
+ if (e.preferenceName === 'editor.scrollBeyondLastLine') {
86
+ this.scrollBeyondLastLine = Boolean(e.newValue);
87
+ this.forceUpdate();
88
+ }
89
+ }));
90
+ this.toDispose.push(this.resource);
91
+ if (this.resource.onDidChangeContents) {
92
+ this.toDispose.push(this.resource.onDidChangeContents(() => this.update()));
93
+ }
94
+ const updateIfAffected = (affectedUri?: string) => {
95
+ if (!affectedUri || affectedUri === this.uri.toString()) {
96
+ this.update();
97
+ }
98
+ };
99
+ this.toDispose.push(this.workspace.onDidOpenTextDocument(document => updateIfAffected(document.uri)));
100
+ this.toDispose.push(this.workspace.onDidChangeTextDocument(params => updateIfAffected(params.model.uri)));
101
+ this.toDispose.push(this.workspace.onDidCloseTextDocument(document => updateIfAffected(document.uri)));
102
+ this.toDispose.push(this.themeService.onDidColorThemeChange(() => this.update()));
103
+ this.firstUpdate = () => {
104
+ this.revealFragment(this.uri);
105
+ };
106
+ this.update();
107
+ }
108
+
109
+ protected override onBeforeAttach(msg: Message): void {
110
+ super.onBeforeAttach(msg);
111
+ this.toDispose.push(this.startScrollSync());
112
+ this.toDispose.push(this.startDoubleClickListener());
113
+ }
114
+
115
+ protected preventScrollNotification: boolean = false;
116
+ protected startScrollSync(): Disposable {
117
+ return addEventListener(this.node, 'scroll', throttle((event: UIEvent) => {
118
+ if (this.preventScrollNotification) {
119
+ return;
120
+ }
121
+ const scrollTop = this.node.scrollTop;
122
+ this.didScroll(scrollTop);
123
+ }, 50));
124
+ }
125
+
126
+ protected startDoubleClickListener(): Disposable {
127
+ return addEventListener(this.node, 'dblclick', (event: MouseEvent) => {
128
+ if (!(event.target instanceof HTMLElement)) {
129
+ return;
130
+ }
131
+ const target = event.target as HTMLElement;
132
+ let node: HTMLElement | null = target;
133
+ while (node && node instanceof HTMLElement) {
134
+ if (node.tagName === 'A') {
135
+ return;
136
+ }
137
+ node = node.parentElement;
138
+ }
139
+ const offsetParent = target.offsetParent as HTMLElement;
140
+ const offset = offsetParent.classList.contains(PREVIEW_WIDGET_CLASS) ? target.offsetTop : offsetParent.offsetTop;
141
+ this.didDoubleClick(offset);
142
+ });
143
+ }
144
+
145
+ getUri(): URI {
146
+ return this.uri;
147
+ }
148
+
149
+ getResourceUri(): URI | undefined {
150
+ return this.uri;
151
+ }
152
+ createMoveToUri(resourceUri: URI): URI | undefined {
153
+ return this.uri.withPath(resourceUri.path);
154
+ }
155
+
156
+ override onActivateRequest(msg: Message): void {
157
+ super.onActivateRequest(msg);
158
+ this.node.focus();
159
+ this.update();
160
+ }
161
+
162
+ override onUpdateRequest(msg: Message): void {
163
+ super.onUpdateRequest(msg);
164
+ this.performUpdate();
165
+ }
166
+
167
+ protected forceUpdate(): void {
168
+ this.previousContent = undefined;
169
+ this.update();
170
+ }
171
+
172
+ protected previousContent: string | undefined = undefined;
173
+ protected async performUpdate(): Promise<void> {
174
+ if (!this.resource) {
175
+ return;
176
+ }
177
+ const uri = this.resource.uri;
178
+ const document = this.workspace.textDocuments.find(d => d.uri === uri.toString());
179
+ const content: MaybePromise<string> = document ? document.getText() : await this.resource.readContents();
180
+ if (content === this.previousContent) {
181
+ return;
182
+ }
183
+ this.previousContent = content;
184
+ const contentElement = await this.render(content, uri);
185
+ this.node.innerHTML = '';
186
+ if (contentElement) {
187
+ if (this.scrollBeyondLastLine) {
188
+ contentElement.classList.add('scrollBeyondLastLine');
189
+ }
190
+ this.node.appendChild(contentElement);
191
+ if (this.firstUpdate) {
192
+ this.firstUpdate();
193
+ this.firstUpdate = undefined;
194
+ }
195
+ }
196
+ }
197
+
198
+ protected async render(content: string, originUri: URI): Promise<HTMLElement | undefined> {
199
+ if (!this.previewHandler || !this.resource) {
200
+ return undefined;
201
+ }
202
+ return this.previewHandler.renderContent({ content, originUri });
203
+ }
204
+
205
+ protected revealFragment(uri: URI): void {
206
+ if (uri.fragment === '' || !this.previewHandler || !this.previewHandler.findElementForFragment) {
207
+ return;
208
+ }
209
+ const elementToReveal = this.previewHandler.findElementForFragment(this.node, uri.fragment);
210
+ if (elementToReveal) {
211
+ this.preventScrollNotification = true;
212
+ elementToReveal.scrollIntoView();
213
+ window.setTimeout(() => {
214
+ this.preventScrollNotification = false;
215
+ }, 50);
216
+ }
217
+ }
218
+
219
+ revealForSourceLine(sourceLine: number): void {
220
+ this.internalRevealForSourceLine(sourceLine);
221
+ }
222
+ protected readonly internalRevealForSourceLine: (sourceLine: number) => void = throttle((sourceLine: number) => {
223
+ if (!this.previewHandler || !this.previewHandler.findElementForSourceLine) {
224
+ return;
225
+ }
226
+ const elementToReveal = this.previewHandler.findElementForSourceLine(this.node, sourceLine);
227
+ if (elementToReveal) {
228
+ this.preventScrollNotification = true;
229
+ elementToReveal.scrollIntoView();
230
+ window.setTimeout(() => {
231
+ this.preventScrollNotification = false;
232
+ }, 50);
233
+ }
234
+ }, 50);
235
+
236
+ get onDidScroll(): Event<number> {
237
+ return this.onDidScrollEmitter.event;
238
+ }
239
+
240
+ protected fireDidScrollToSourceLine(line: number): void {
241
+ this.onDidScrollEmitter.fire(line);
242
+ }
243
+
244
+ protected didScroll(scrollTop: number): void {
245
+ if (!this.previewHandler || !this.previewHandler.getSourceLineForOffset) {
246
+ return;
247
+ }
248
+ const offset = scrollTop;
249
+ const line = this.previewHandler.getSourceLineForOffset(this.node, offset);
250
+ if (line) {
251
+ this.fireDidScrollToSourceLine(line);
252
+ }
253
+ }
254
+
255
+ get onDidDoubleClick(): Event<Location> {
256
+ return this.onDidDoubleClickEmitter.event;
257
+ }
258
+
259
+ protected fireDidDoubleClickToSourceLine(line: number): void {
260
+ if (!this.resource) {
261
+ return;
262
+ }
263
+ this.onDidDoubleClickEmitter.fire({
264
+ uri: this.resource.uri.toString(),
265
+ range: Range.create({ line, character: 0 }, { line, character: 0 })
266
+ });
267
+ }
268
+
269
+ protected didDoubleClick(offsetTop: number): void {
270
+ if (!this.previewHandler || !this.previewHandler.getSourceLineForOffset) {
271
+ return;
272
+ }
273
+ const line = this.previewHandler.getSourceLineForOffset(this.node, offsetTop) || 0;
274
+ this.fireDidDoubleClickToSourceLine(line);
275
+ }
276
+
277
+ }