-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathpreview.ts
More file actions
205 lines (183 loc) · 5.68 KB
/
preview.ts
File metadata and controls
205 lines (183 loc) · 5.68 KB
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
import type { Denops } from "@denops/std";
import * as fn from "@denops/std/function";
import * as buffer from "@denops/std/buffer";
import { batch } from "@denops/std/batch";
import type { Dimension } from "@vim-fall/core/coordinator";
import { BaseComponent, type ComponentProperties } from "./_component.ts";
/**
* Parameters for configuring a PreviewComponent
*/
export type PreviewComponentParams = ComponentProperties & {
/** If true, enables real syntax highlighting within the preview */
readonly realHighlight?: boolean;
};
/**
* Defines the structure of an item to be previewed
*/
export type PreviewItem = {
/** Content to display in the preview as an array of strings */
readonly content: readonly string[];
/** Optional filename associated with the previewed content */
readonly filename?: string;
/** Optional filetype for applying syntax highlighting */
readonly filetype?: string;
/** Optional line number to position the cursor */
readonly line?: number;
/** Optional column number to position the cursor */
readonly column?: number;
};
/**
* Represents a preview component that displays content with optional
* syntax highlighting and cursor positioning.
*/
export class PreviewComponent extends BaseComponent {
readonly #realHighlight: boolean;
#item?: PreviewItem;
#modifiedContent = true;
#reservedCommands: string[] = [];
constructor({ realHighlight, ...params }: PreviewComponentParams = {}) {
super(params);
this.#realHighlight = realHighlight ?? false;
}
/** Gets the current preview item */
get item(): PreviewItem | undefined {
return this.#item;
}
/** Sets a new preview item, marking the content as modified */
set item(value: PreviewItem | undefined) {
this.#item = value;
this.#modifiedContent = true;
}
/**
* Adds a command to be executed in the preview window context.
*
* @param command - The Vim command to be executed
*/
execute(command: string): void {
this.#reservedCommands.push(command);
}
/** Forces a re-render of the preview content */
forceRender(): void {
this.#modifiedContent = true;
}
override async open(
denops: Denops,
dimension: Readonly<Dimension>,
{ signal }: { signal?: AbortSignal } = {},
): Promise<AsyncDisposable> {
await using stack = new AsyncDisposableStack();
stack.use(await super.open(denops, dimension, { signal }));
const { winid } = this.info!;
signal?.throwIfAborted();
await fn.win_execute(denops, winid, "setlocal signcolumn=no nofoldenable");
this.forceRender();
return stack.move();
}
override async render(
denops: Denops,
{ signal }: { signal?: AbortSignal } = {},
): Promise<true | void> {
try {
const results = [
await this.#renderContent(denops, { signal }),
await this.#executeCommands(denops, { signal }),
];
return results.some((result) => result) ? true : undefined;
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") return;
const m = err instanceof Error ? err.message : String(err);
console.warn(`Failed to render content of the preview component: ${m}`);
}
}
async #renderContent(
denops: Denops,
{ signal }: { signal?: AbortSignal } = {},
): Promise<true | void> {
if (!this.#modifiedContent || !this.info) return;
this.#modifiedContent = false;
const { winid, bufnr } = this.info;
const {
content = ["No preview"],
filename = "noname",
filetype = "",
line = 1,
column = 1,
} = this.#item ?? {};
signal?.throwIfAborted();
await buffer.replace(denops, bufnr, content as string[]);
signal?.throwIfAborted();
await batch(denops, async (denops) => {
// Clear previous buffer context
await fn.win_execute(
denops,
winid,
`silent! 0file`,
);
await fn.win_execute(
denops,
winid,
`silent! syntax clear`,
);
// Change buffer name and reset options
await fn.win_execute(
denops,
winid,
`silent! file fall://preview/${filename}`,
);
await fn.win_execute(
denops,
winid,
`silent! setlocal winfixbuf winfixwidth winfixheight`,
);
// Apply highlight
if (this.#realHighlight) {
await fn.win_execute(
denops,
winid,
`call fall#internal#highlight#real('${filetype}')`,
);
} else {
await fn.win_execute(
denops,
winid,
`call fall#internal#highlight#fast('${filetype}')`,
);
}
// Overwrite buffer local options may configured by ftplugin
await fn.win_execute(
denops,
winid,
`silent! setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nomodifiable nowrap cursorline cursorlineopt=number,line`,
);
// Move cursor
await fn.win_execute(
denops,
winid,
`silent! normal! ${line}G${column}|`,
);
// Emit autocmd for customization
await fn.win_execute(
denops,
winid,
`silent! doautocmd <nomodeline> User FallPreviewRendered:${filename}`,
);
});
return true;
}
async #executeCommands(
denops: Denops,
{ signal }: { signal?: AbortSignal } = {},
): Promise<true | void> {
if (!this.#reservedCommands.length || !this.info) return;
const reservedCommands = this.#reservedCommands;
this.#reservedCommands = [];
const { winid } = this.info;
signal?.throwIfAborted();
await batch(denops, async (denops) => {
for (const command of reservedCommands) {
await fn.win_execute(denops, winid, command);
}
});
return true;
}
}