@fresh-editor/fresh-editor 0.1.13 → 0.1.15
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.
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -2
- package/plugins/buffer_modified.ts +34 -0
- package/plugins/lib/fresh.d.ts +9 -0
- package/plugins/markdown_compose.ts +13 -832
package/npm-shrinkwrap.json
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"hasInstallScript": true,
|
|
24
24
|
"license": "GPL-2.0",
|
|
25
25
|
"name": "@fresh-editor/fresh-editor",
|
|
26
|
-
"version": "0.1.
|
|
26
|
+
"version": "0.1.15"
|
|
27
27
|
},
|
|
28
28
|
"node_modules/@isaacs/balanced-match": {
|
|
29
29
|
"engines": {
|
|
@@ -896,5 +896,5 @@
|
|
|
896
896
|
}
|
|
897
897
|
},
|
|
898
898
|
"requires": true,
|
|
899
|
-
"version": "0.1.
|
|
899
|
+
"version": "0.1.15"
|
|
900
900
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"artifactDownloadUrl": "https://github.com/sinelaw/fresh/releases/download/v0.1.
|
|
2
|
+
"artifactDownloadUrl": "https://github.com/sinelaw/fresh/releases/download/v0.1.15",
|
|
3
3
|
"author": "Noam Lewis",
|
|
4
4
|
"bin": {
|
|
5
5
|
"fresh": "run-fresh.js"
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"zipExt": ".tar.xz"
|
|
93
93
|
}
|
|
94
94
|
},
|
|
95
|
-
"version": "0.1.
|
|
95
|
+
"version": "0.1.15",
|
|
96
96
|
"volta": {
|
|
97
97
|
"node": "18.14.1",
|
|
98
98
|
"npm": "9.5.0"
|
|
@@ -94,6 +94,38 @@ function markLinesModified(bufferId: number, startLine: number, endLine: number)
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
function reapplyIndicatorsFromDiff(bufferId: number): void {
|
|
98
|
+
const diff = editor.getBufferSavedDiff(bufferId);
|
|
99
|
+
if (!diff) return;
|
|
100
|
+
|
|
101
|
+
// If buffer matches saved snapshot, clear everything.
|
|
102
|
+
if (diff.equal) {
|
|
103
|
+
editor.clearLineIndicators(bufferId, NAMESPACE);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const ranges = diff.line_ranges;
|
|
108
|
+
// If line info is unavailable, leave existing indicators (best effort).
|
|
109
|
+
if (!ranges) return;
|
|
110
|
+
|
|
111
|
+
// Reset namespace to drop stale indicators outside the changed ranges.
|
|
112
|
+
editor.clearLineIndicators(bufferId, NAMESPACE);
|
|
113
|
+
for (const [start, end] of ranges) {
|
|
114
|
+
for (let line = start; line < end; line++) {
|
|
115
|
+
editor.setLineIndicator(
|
|
116
|
+
bufferId,
|
|
117
|
+
line,
|
|
118
|
+
NAMESPACE,
|
|
119
|
+
SYMBOL,
|
|
120
|
+
COLOR[0],
|
|
121
|
+
COLOR[1],
|
|
122
|
+
COLOR[2],
|
|
123
|
+
PRIORITY
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
97
129
|
// =============================================================================
|
|
98
130
|
// Event Handlers
|
|
99
131
|
// =============================================================================
|
|
@@ -179,6 +211,7 @@ globalThis.onBufferModifiedAfterInsert = function (args: {
|
|
|
179
211
|
// Mark all affected lines (from start_line to end_line inclusive)
|
|
180
212
|
// The indicator markers will automatically track their positions
|
|
181
213
|
markLinesModified(bufferId, args.start_line, args.end_line);
|
|
214
|
+
reapplyIndicatorsFromDiff(bufferId);
|
|
182
215
|
|
|
183
216
|
return true;
|
|
184
217
|
};
|
|
@@ -209,6 +242,7 @@ globalThis.onBufferModifiedAfterDelete = function (args: {
|
|
|
209
242
|
// Mark the line where deletion occurred
|
|
210
243
|
// Markers for deleted lines are automatically cleaned up
|
|
211
244
|
markLinesModified(bufferId, args.start_line, args.start_line);
|
|
245
|
+
reapplyIndicatorsFromDiff(bufferId);
|
|
212
246
|
|
|
213
247
|
return true;
|
|
214
248
|
};
|
package/plugins/lib/fresh.d.ts
CHANGED
|
@@ -123,6 +123,13 @@ interface BufferInfo {
|
|
|
123
123
|
length: number;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/** Diff vs last save for a buffer */
|
|
127
|
+
interface TsBufferSavedDiff {
|
|
128
|
+
equal: boolean;
|
|
129
|
+
byte_ranges: [number, number][];
|
|
130
|
+
line_ranges?: [number, number][] | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
126
133
|
/** Selection range */
|
|
127
134
|
interface SelectionRange {
|
|
128
135
|
/** Start byte position */
|
|
@@ -341,6 +348,8 @@ interface EditorAPI {
|
|
|
341
348
|
* @returns true if process is running, false if not found or exited
|
|
342
349
|
*/
|
|
343
350
|
isProcessRunning(#[bigint] process_id: number): boolean;
|
|
351
|
+
/** Get diff vs last saved snapshot for a buffer */
|
|
352
|
+
getBufferSavedDiff(buffer_id: number): TsBufferSavedDiff | null;
|
|
344
353
|
|
|
345
354
|
// === Buffer Info Queries ===
|
|
346
355
|
/**
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// Markdown Compose Mode Plugin
|
|
2
|
-
// Provides
|
|
3
|
-
// -
|
|
4
|
-
// -
|
|
2
|
+
// Provides compose mode for Markdown documents with:
|
|
3
|
+
// - Soft wrapping at a configurable width
|
|
4
|
+
// - Hanging indents for lists and block quotes
|
|
5
|
+
// - Centered margins
|
|
6
|
+
//
|
|
7
|
+
// Syntax highlighting is handled by the TextMate grammar (built-in to the editor)
|
|
8
|
+
// This plugin only adds the compose mode layout features.
|
|
5
9
|
|
|
6
10
|
interface MarkdownConfig {
|
|
7
11
|
composeWidth: number;
|
|
@@ -15,52 +19,9 @@ const config: MarkdownConfig = {
|
|
|
15
19
|
hideLineNumbers: true,
|
|
16
20
|
};
|
|
17
21
|
|
|
18
|
-
// Track buffers with highlighting enabled (auto for markdown files)
|
|
19
|
-
const highlightingBuffers = new Set<number>();
|
|
20
|
-
|
|
21
22
|
// Track buffers in compose mode (explicit toggle)
|
|
22
23
|
const composeBuffers = new Set<number>();
|
|
23
24
|
|
|
24
|
-
// Track which buffers need their overlays refreshed (content changed)
|
|
25
|
-
const dirtyBuffers = new Set<number>();
|
|
26
|
-
|
|
27
|
-
// Markdown token types for parsing
|
|
28
|
-
enum TokenType {
|
|
29
|
-
Header1,
|
|
30
|
-
Header2,
|
|
31
|
-
Header3,
|
|
32
|
-
Header4,
|
|
33
|
-
Header5,
|
|
34
|
-
Header6,
|
|
35
|
-
ListItem,
|
|
36
|
-
OrderedListItem,
|
|
37
|
-
Checkbox,
|
|
38
|
-
CodeBlockFence,
|
|
39
|
-
CodeBlockContent,
|
|
40
|
-
BlockQuote,
|
|
41
|
-
HorizontalRule,
|
|
42
|
-
Paragraph,
|
|
43
|
-
HardBreak,
|
|
44
|
-
Image, // Images should have hard breaks (not soft breaks)
|
|
45
|
-
InlineCode,
|
|
46
|
-
Bold,
|
|
47
|
-
Italic,
|
|
48
|
-
Strikethrough,
|
|
49
|
-
Link,
|
|
50
|
-
LinkText,
|
|
51
|
-
LinkUrl,
|
|
52
|
-
Text,
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface Token {
|
|
56
|
-
type: TokenType;
|
|
57
|
-
start: number; // byte offset
|
|
58
|
-
end: number; // byte offset
|
|
59
|
-
text: string;
|
|
60
|
-
level?: number; // For headers, list indentation
|
|
61
|
-
checked?: boolean; // For checkboxes
|
|
62
|
-
}
|
|
63
|
-
|
|
64
25
|
// Types match the Rust ViewTokenWire structure
|
|
65
26
|
interface ViewTokenWire {
|
|
66
27
|
source_offset: number | null;
|
|
@@ -343,669 +304,6 @@ function parseMarkdownBlocks(text: string): ParsedBlock[] {
|
|
|
343
304
|
return blocks;
|
|
344
305
|
}
|
|
345
306
|
|
|
346
|
-
// Colors for styling (RGB tuples)
|
|
347
|
-
const COLORS = {
|
|
348
|
-
header: [100, 149, 237] as [number, number, number], // Cornflower blue
|
|
349
|
-
code: [152, 195, 121] as [number, number, number], // Green
|
|
350
|
-
codeBlock: [152, 195, 121] as [number, number, number],
|
|
351
|
-
fence: [80, 80, 80] as [number, number, number], // Subdued gray for ```
|
|
352
|
-
link: [86, 156, 214] as [number, number, number], // Light blue
|
|
353
|
-
linkUrl: [80, 80, 80] as [number, number, number], // Subdued gray
|
|
354
|
-
bold: [255, 255, 220] as [number, number, number], // Bright for bold text
|
|
355
|
-
boldMarker: [80, 80, 80] as [number, number, number], // Subdued for ** markers
|
|
356
|
-
italic: [198, 180, 221] as [number, number, number], // Light purple for italic
|
|
357
|
-
italicMarker: [80, 80, 80] as [number, number, number], // Subdued for * markers
|
|
358
|
-
quote: [128, 128, 128] as [number, number, number], // Gray
|
|
359
|
-
checkbox: [152, 195, 121] as [number, number, number], // Green
|
|
360
|
-
listBullet: [86, 156, 214] as [number, number, number], // Light blue
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
// Simple Markdown parser
|
|
364
|
-
class MarkdownParser {
|
|
365
|
-
private text: string;
|
|
366
|
-
private tokens: Token[] = [];
|
|
367
|
-
|
|
368
|
-
constructor(text: string) {
|
|
369
|
-
this.text = text;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
parse(): Token[] {
|
|
373
|
-
const lines = this.text.split('\n');
|
|
374
|
-
let byteOffset = 0;
|
|
375
|
-
let inCodeBlock = false;
|
|
376
|
-
let codeFenceStart = -1;
|
|
377
|
-
|
|
378
|
-
for (let i = 0; i < lines.length; i++) {
|
|
379
|
-
const line = lines[i];
|
|
380
|
-
const lineStart = byteOffset;
|
|
381
|
-
const lineEnd = byteOffset + line.length;
|
|
382
|
-
|
|
383
|
-
// Code block detection
|
|
384
|
-
if (line.trim().startsWith('```')) {
|
|
385
|
-
if (!inCodeBlock) {
|
|
386
|
-
inCodeBlock = true;
|
|
387
|
-
codeFenceStart = lineStart;
|
|
388
|
-
this.tokens.push({
|
|
389
|
-
type: TokenType.CodeBlockFence,
|
|
390
|
-
start: lineStart,
|
|
391
|
-
end: lineEnd,
|
|
392
|
-
text: line,
|
|
393
|
-
});
|
|
394
|
-
} else {
|
|
395
|
-
this.tokens.push({
|
|
396
|
-
type: TokenType.CodeBlockFence,
|
|
397
|
-
start: lineStart,
|
|
398
|
-
end: lineEnd,
|
|
399
|
-
text: line,
|
|
400
|
-
});
|
|
401
|
-
inCodeBlock = false;
|
|
402
|
-
}
|
|
403
|
-
} else if (inCodeBlock) {
|
|
404
|
-
this.tokens.push({
|
|
405
|
-
type: TokenType.CodeBlockContent,
|
|
406
|
-
start: lineStart,
|
|
407
|
-
end: lineEnd,
|
|
408
|
-
text: line,
|
|
409
|
-
});
|
|
410
|
-
} else {
|
|
411
|
-
// Parse line structure
|
|
412
|
-
this.parseLine(line, lineStart, lineEnd);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
byteOffset = lineEnd + 1; // +1 for newline
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// Parse inline styles after structure
|
|
419
|
-
this.parseInlineStyles();
|
|
420
|
-
|
|
421
|
-
return this.tokens;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
private parseLine(line: string, start: number, end: number): void {
|
|
425
|
-
const trimmed = line.trim();
|
|
426
|
-
|
|
427
|
-
// Headers
|
|
428
|
-
const headerMatch = trimmed.match(/^(#{1,6})\s+(.+)$/);
|
|
429
|
-
if (headerMatch) {
|
|
430
|
-
const level = headerMatch[1].length;
|
|
431
|
-
const type = [
|
|
432
|
-
TokenType.Header1,
|
|
433
|
-
TokenType.Header2,
|
|
434
|
-
TokenType.Header3,
|
|
435
|
-
TokenType.Header4,
|
|
436
|
-
TokenType.Header5,
|
|
437
|
-
TokenType.Header6,
|
|
438
|
-
][level - 1];
|
|
439
|
-
this.tokens.push({
|
|
440
|
-
type,
|
|
441
|
-
start,
|
|
442
|
-
end,
|
|
443
|
-
text: line,
|
|
444
|
-
level,
|
|
445
|
-
});
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Horizontal rule
|
|
450
|
-
if (trimmed.match(/^(-{3,}|\*{3,}|_{3,})$/)) {
|
|
451
|
-
this.tokens.push({
|
|
452
|
-
type: TokenType.HorizontalRule,
|
|
453
|
-
start,
|
|
454
|
-
end,
|
|
455
|
-
text: line,
|
|
456
|
-
});
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// List items
|
|
461
|
-
const bulletMatch = line.match(/^(\s*)([-*+])\s+(.*)$/);
|
|
462
|
-
if (bulletMatch) {
|
|
463
|
-
const indent = bulletMatch[1].length;
|
|
464
|
-
const hasCheckbox = bulletMatch[3].match(/^\[([ x])\]\s+/);
|
|
465
|
-
|
|
466
|
-
if (hasCheckbox) {
|
|
467
|
-
this.tokens.push({
|
|
468
|
-
type: TokenType.Checkbox,
|
|
469
|
-
start,
|
|
470
|
-
end,
|
|
471
|
-
text: line,
|
|
472
|
-
level: indent,
|
|
473
|
-
checked: hasCheckbox[1] === 'x',
|
|
474
|
-
});
|
|
475
|
-
} else {
|
|
476
|
-
this.tokens.push({
|
|
477
|
-
type: TokenType.ListItem,
|
|
478
|
-
start,
|
|
479
|
-
end,
|
|
480
|
-
text: line,
|
|
481
|
-
level: indent,
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
return;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Ordered list
|
|
488
|
-
const orderedMatch = line.match(/^(\s*)(\d+\.)\s+(.*)$/);
|
|
489
|
-
if (orderedMatch) {
|
|
490
|
-
const indent = orderedMatch[1].length;
|
|
491
|
-
this.tokens.push({
|
|
492
|
-
type: TokenType.OrderedListItem,
|
|
493
|
-
start,
|
|
494
|
-
end,
|
|
495
|
-
text: line,
|
|
496
|
-
level: indent,
|
|
497
|
-
});
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Block quote
|
|
502
|
-
if (trimmed.startsWith('>')) {
|
|
503
|
-
this.tokens.push({
|
|
504
|
-
type: TokenType.BlockQuote,
|
|
505
|
-
start,
|
|
506
|
-
end,
|
|
507
|
-
text: line,
|
|
508
|
-
});
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Hard breaks (two spaces + newline, or backslash + newline)
|
|
513
|
-
if (line.endsWith(' ') || line.endsWith('\\')) {
|
|
514
|
-
this.tokens.push({
|
|
515
|
-
type: TokenType.HardBreak,
|
|
516
|
-
start,
|
|
517
|
-
end,
|
|
518
|
-
text: line,
|
|
519
|
-
});
|
|
520
|
-
return;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Images:  - these should have hard breaks to keep each on its own line
|
|
524
|
-
if (trimmed.match(/^!\[.*\]\(.*\)$/)) {
|
|
525
|
-
this.tokens.push({
|
|
526
|
-
type: TokenType.Image,
|
|
527
|
-
start,
|
|
528
|
-
end,
|
|
529
|
-
text: line,
|
|
530
|
-
});
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
// Default: paragraph
|
|
535
|
-
if (trimmed.length > 0) {
|
|
536
|
-
this.tokens.push({
|
|
537
|
-
type: TokenType.Paragraph,
|
|
538
|
-
start,
|
|
539
|
-
end,
|
|
540
|
-
text: line,
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
private parseInlineStyles(): void {
|
|
546
|
-
// Parse inline markdown (bold, italic, code, links) within text
|
|
547
|
-
// This is a simplified parser - a full implementation would use a proper MD parser
|
|
548
|
-
|
|
549
|
-
for (const token of this.tokens) {
|
|
550
|
-
if (token.type === TokenType.Paragraph ||
|
|
551
|
-
token.type === TokenType.ListItem ||
|
|
552
|
-
token.type === TokenType.OrderedListItem) {
|
|
553
|
-
// Find inline code
|
|
554
|
-
this.findInlineCode(token);
|
|
555
|
-
// Find bold/italic
|
|
556
|
-
this.findEmphasis(token);
|
|
557
|
-
// Find links
|
|
558
|
-
this.findLinks(token);
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
private findInlineCode(token: Token): void {
|
|
564
|
-
const regex = /`([^`]+)`/g;
|
|
565
|
-
let match;
|
|
566
|
-
while ((match = regex.exec(token.text)) !== null) {
|
|
567
|
-
this.tokens.push({
|
|
568
|
-
type: TokenType.InlineCode,
|
|
569
|
-
start: token.start + match.index,
|
|
570
|
-
end: token.start + match.index + match[0].length,
|
|
571
|
-
text: match[0],
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
private findEmphasis(token: Token): void {
|
|
577
|
-
// Bold: **text** or __text__
|
|
578
|
-
const boldRegex = /(\*\*|__)([^*_]+)\1/g;
|
|
579
|
-
let match;
|
|
580
|
-
while ((match = boldRegex.exec(token.text)) !== null) {
|
|
581
|
-
this.tokens.push({
|
|
582
|
-
type: TokenType.Bold,
|
|
583
|
-
start: token.start + match.index,
|
|
584
|
-
end: token.start + match.index + match[0].length,
|
|
585
|
-
text: match[0],
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// Italic: *text* or _text_
|
|
590
|
-
const italicRegex = /(\*|_)([^*_]+)\1/g;
|
|
591
|
-
while ((match = italicRegex.exec(token.text)) !== null) {
|
|
592
|
-
// Skip if it's part of bold
|
|
593
|
-
const isBold = this.tokens.some(t =>
|
|
594
|
-
t.type === TokenType.Bold &&
|
|
595
|
-
t.start <= token.start + match.index &&
|
|
596
|
-
t.end >= token.start + match.index + match[0].length
|
|
597
|
-
);
|
|
598
|
-
if (!isBold) {
|
|
599
|
-
this.tokens.push({
|
|
600
|
-
type: TokenType.Italic,
|
|
601
|
-
start: token.start + match.index,
|
|
602
|
-
end: token.start + match.index + match[0].length,
|
|
603
|
-
text: match[0],
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// Strikethrough: ~~text~~
|
|
609
|
-
const strikeRegex = /~~([^~]+)~~/g;
|
|
610
|
-
while ((match = strikeRegex.exec(token.text)) !== null) {
|
|
611
|
-
this.tokens.push({
|
|
612
|
-
type: TokenType.Strikethrough,
|
|
613
|
-
start: token.start + match.index,
|
|
614
|
-
end: token.start + match.index + match[0].length,
|
|
615
|
-
text: match[0],
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
private findLinks(token: Token): void {
|
|
621
|
-
// Links: [text](url)
|
|
622
|
-
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
623
|
-
let match;
|
|
624
|
-
while ((match = linkRegex.exec(token.text)) !== null) {
|
|
625
|
-
const fullStart = token.start + match.index;
|
|
626
|
-
const textStart = fullStart + 1; // After [
|
|
627
|
-
const textEnd = textStart + match[1].length;
|
|
628
|
-
const urlStart = textEnd + 2; // After ](
|
|
629
|
-
const urlEnd = urlStart + match[2].length;
|
|
630
|
-
|
|
631
|
-
this.tokens.push({
|
|
632
|
-
type: TokenType.Link,
|
|
633
|
-
start: fullStart,
|
|
634
|
-
end: fullStart + match[0].length,
|
|
635
|
-
text: match[0],
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
this.tokens.push({
|
|
639
|
-
type: TokenType.LinkText,
|
|
640
|
-
start: textStart,
|
|
641
|
-
end: textEnd,
|
|
642
|
-
text: match[1],
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
this.tokens.push({
|
|
646
|
-
type: TokenType.LinkUrl,
|
|
647
|
-
start: urlStart,
|
|
648
|
-
end: urlEnd,
|
|
649
|
-
text: match[2],
|
|
650
|
-
});
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Apply styling overlays based on parsed tokens
|
|
656
|
-
function applyMarkdownStyling(bufferId: number, tokens: Token[]): void {
|
|
657
|
-
// Clear existing markdown overlays
|
|
658
|
-
editor.clearNamespace(bufferId, "md");
|
|
659
|
-
|
|
660
|
-
for (const token of tokens) {
|
|
661
|
-
let color: [number, number, number] | null = null;
|
|
662
|
-
let underline = false;
|
|
663
|
-
let overlayId = "md";
|
|
664
|
-
|
|
665
|
-
switch (token.type) {
|
|
666
|
-
case TokenType.Header1:
|
|
667
|
-
case TokenType.Header2:
|
|
668
|
-
case TokenType.Header3:
|
|
669
|
-
case TokenType.Header4:
|
|
670
|
-
case TokenType.Header5:
|
|
671
|
-
case TokenType.Header6:
|
|
672
|
-
color = COLORS.header;
|
|
673
|
-
underline = true;
|
|
674
|
-
break;
|
|
675
|
-
|
|
676
|
-
case TokenType.InlineCode:
|
|
677
|
-
color = COLORS.code;
|
|
678
|
-
break;
|
|
679
|
-
|
|
680
|
-
case TokenType.CodeBlockFence:
|
|
681
|
-
color = COLORS.fence;
|
|
682
|
-
break;
|
|
683
|
-
|
|
684
|
-
case TokenType.CodeBlockContent:
|
|
685
|
-
color = COLORS.codeBlock;
|
|
686
|
-
break;
|
|
687
|
-
|
|
688
|
-
case TokenType.BlockQuote:
|
|
689
|
-
color = COLORS.quote;
|
|
690
|
-
break;
|
|
691
|
-
|
|
692
|
-
case TokenType.Bold:
|
|
693
|
-
// Style bold markers (** or __) subdued, content bold
|
|
694
|
-
const boldMatch = token.text.match(/^(\*\*|__)(.*)(\*\*|__)$/);
|
|
695
|
-
if (boldMatch) {
|
|
696
|
-
const markerLen = boldMatch[1].length;
|
|
697
|
-
// Subdued markers
|
|
698
|
-
editor.addOverlay(bufferId, "md",
|
|
699
|
-
token.start, token.start + markerLen,
|
|
700
|
-
COLORS.boldMarker[0], COLORS.boldMarker[1], COLORS.boldMarker[2], false, false, false);
|
|
701
|
-
editor.addOverlay(bufferId, "md",
|
|
702
|
-
token.end - markerLen, token.end,
|
|
703
|
-
COLORS.boldMarker[0], COLORS.boldMarker[1], COLORS.boldMarker[2], false, false, false);
|
|
704
|
-
// Bold content with bold=true
|
|
705
|
-
editor.addOverlay(bufferId, "md",
|
|
706
|
-
token.start + markerLen, token.end - markerLen,
|
|
707
|
-
COLORS.bold[0], COLORS.bold[1], COLORS.bold[2], false, true, false);
|
|
708
|
-
} else {
|
|
709
|
-
color = COLORS.bold;
|
|
710
|
-
}
|
|
711
|
-
break;
|
|
712
|
-
|
|
713
|
-
case TokenType.Italic:
|
|
714
|
-
// Style italic markers (* or _) subdued, content italic
|
|
715
|
-
const italicMatch = token.text.match(/^(\*|_)(.*)(\*|_)$/);
|
|
716
|
-
if (italicMatch) {
|
|
717
|
-
const markerLen = 1;
|
|
718
|
-
// Subdued markers
|
|
719
|
-
editor.addOverlay(bufferId, "md",
|
|
720
|
-
token.start, token.start + markerLen,
|
|
721
|
-
COLORS.italicMarker[0], COLORS.italicMarker[1], COLORS.italicMarker[2], false, false, false);
|
|
722
|
-
editor.addOverlay(bufferId, "md",
|
|
723
|
-
token.end - markerLen, token.end,
|
|
724
|
-
COLORS.italicMarker[0], COLORS.italicMarker[1], COLORS.italicMarker[2], false, false, false);
|
|
725
|
-
// Italic content with italic=true
|
|
726
|
-
editor.addOverlay(bufferId, "md",
|
|
727
|
-
token.start + markerLen, token.end - markerLen,
|
|
728
|
-
COLORS.italic[0], COLORS.italic[1], COLORS.italic[2], false, false, true);
|
|
729
|
-
} else {
|
|
730
|
-
color = COLORS.italic;
|
|
731
|
-
}
|
|
732
|
-
break;
|
|
733
|
-
|
|
734
|
-
case TokenType.LinkText:
|
|
735
|
-
color = COLORS.link;
|
|
736
|
-
underline = true;
|
|
737
|
-
break;
|
|
738
|
-
|
|
739
|
-
case TokenType.LinkUrl:
|
|
740
|
-
color = COLORS.linkUrl;
|
|
741
|
-
break;
|
|
742
|
-
|
|
743
|
-
case TokenType.ListItem:
|
|
744
|
-
case TokenType.OrderedListItem:
|
|
745
|
-
// Style just the bullet/number
|
|
746
|
-
const bulletMatch = token.text.match(/^(\s*)([-*+]|\d+\.)/);
|
|
747
|
-
if (bulletMatch) {
|
|
748
|
-
const bulletEnd = token.start + bulletMatch[0].length;
|
|
749
|
-
editor.addOverlay(
|
|
750
|
-
bufferId,
|
|
751
|
-
"md",
|
|
752
|
-
token.start,
|
|
753
|
-
bulletEnd,
|
|
754
|
-
COLORS.listBullet[0],
|
|
755
|
-
COLORS.listBullet[1],
|
|
756
|
-
COLORS.listBullet[2],
|
|
757
|
-
false
|
|
758
|
-
);
|
|
759
|
-
}
|
|
760
|
-
break;
|
|
761
|
-
|
|
762
|
-
case TokenType.Checkbox:
|
|
763
|
-
// Style checkbox and bullet
|
|
764
|
-
const checkboxMatch = token.text.match(/^(\s*[-*+]\s+\[[ x]\])/);
|
|
765
|
-
if (checkboxMatch) {
|
|
766
|
-
const checkboxEnd = token.start + checkboxMatch[0].length;
|
|
767
|
-
editor.addOverlay(
|
|
768
|
-
bufferId,
|
|
769
|
-
"md",
|
|
770
|
-
token.start,
|
|
771
|
-
checkboxEnd,
|
|
772
|
-
COLORS.checkbox[0],
|
|
773
|
-
COLORS.checkbox[1],
|
|
774
|
-
COLORS.checkbox[2],
|
|
775
|
-
false
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
break;
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
if (color) {
|
|
782
|
-
editor.addOverlay(
|
|
783
|
-
bufferId,
|
|
784
|
-
overlayId,
|
|
785
|
-
token.start,
|
|
786
|
-
token.end,
|
|
787
|
-
color[0],
|
|
788
|
-
color[1],
|
|
789
|
-
color[2],
|
|
790
|
-
underline
|
|
791
|
-
);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
// Highlight a single line for markdown (used with lines_changed event)
|
|
797
|
-
function highlightLine(
|
|
798
|
-
bufferId: number,
|
|
799
|
-
lineNumber: number,
|
|
800
|
-
byteStart: number,
|
|
801
|
-
content: string
|
|
802
|
-
): void {
|
|
803
|
-
const trimmed = content.trim();
|
|
804
|
-
if (trimmed.length === 0) return;
|
|
805
|
-
|
|
806
|
-
// Headers
|
|
807
|
-
const headerMatch = trimmed.match(/^(#{1,6})\s/);
|
|
808
|
-
if (headerMatch) {
|
|
809
|
-
editor.addOverlay(
|
|
810
|
-
bufferId,
|
|
811
|
-
"md",
|
|
812
|
-
byteStart,
|
|
813
|
-
byteStart + content.length,
|
|
814
|
-
COLORS.header[0], COLORS.header[1], COLORS.header[2],
|
|
815
|
-
false, true, false // bold
|
|
816
|
-
);
|
|
817
|
-
return;
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
// Code block fences
|
|
821
|
-
if (trimmed.startsWith('```')) {
|
|
822
|
-
editor.addOverlay(
|
|
823
|
-
bufferId,
|
|
824
|
-
"md",
|
|
825
|
-
byteStart,
|
|
826
|
-
byteStart + content.length,
|
|
827
|
-
COLORS.fence[0], COLORS.fence[1], COLORS.fence[2],
|
|
828
|
-
false
|
|
829
|
-
);
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
// Block quotes
|
|
834
|
-
if (trimmed.startsWith('>')) {
|
|
835
|
-
editor.addOverlay(
|
|
836
|
-
bufferId,
|
|
837
|
-
"md",
|
|
838
|
-
byteStart,
|
|
839
|
-
byteStart + content.length,
|
|
840
|
-
COLORS.quote[0], COLORS.quote[1], COLORS.quote[2],
|
|
841
|
-
false
|
|
842
|
-
);
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
// Horizontal rules
|
|
847
|
-
if (trimmed.match(/^[-*_]{3,}$/)) {
|
|
848
|
-
editor.addOverlay(
|
|
849
|
-
bufferId,
|
|
850
|
-
"md",
|
|
851
|
-
byteStart,
|
|
852
|
-
byteStart + content.length,
|
|
853
|
-
COLORS.quote[0], COLORS.quote[1], COLORS.quote[2],
|
|
854
|
-
false
|
|
855
|
-
);
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
// List items (unordered)
|
|
860
|
-
const listMatch = content.match(/^(\s*)([-*+])\s/);
|
|
861
|
-
if (listMatch) {
|
|
862
|
-
const bulletStart = byteStart + listMatch[1].length;
|
|
863
|
-
const bulletEnd = bulletStart + 1;
|
|
864
|
-
editor.addOverlay(
|
|
865
|
-
bufferId,
|
|
866
|
-
"md",
|
|
867
|
-
bulletStart,
|
|
868
|
-
bulletEnd,
|
|
869
|
-
COLORS.listBullet[0], COLORS.listBullet[1], COLORS.listBullet[2],
|
|
870
|
-
false
|
|
871
|
-
);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// Ordered list items
|
|
875
|
-
const orderedMatch = content.match(/^(\s*)(\d+\.)\s/);
|
|
876
|
-
if (orderedMatch) {
|
|
877
|
-
const numStart = byteStart + orderedMatch[1].length;
|
|
878
|
-
const numEnd = numStart + orderedMatch[2].length;
|
|
879
|
-
editor.addOverlay(
|
|
880
|
-
bufferId,
|
|
881
|
-
"md",
|
|
882
|
-
numStart,
|
|
883
|
-
numEnd,
|
|
884
|
-
COLORS.listBullet[0], COLORS.listBullet[1], COLORS.listBullet[2],
|
|
885
|
-
false
|
|
886
|
-
);
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
// Checkboxes
|
|
890
|
-
const checkMatch = content.match(/^(\s*[-*+]\s+)(\[[ x]\])/);
|
|
891
|
-
if (checkMatch) {
|
|
892
|
-
const checkStart = byteStart + checkMatch[1].length;
|
|
893
|
-
const checkEnd = checkStart + checkMatch[2].length;
|
|
894
|
-
editor.addOverlay(
|
|
895
|
-
bufferId,
|
|
896
|
-
"md",
|
|
897
|
-
checkStart,
|
|
898
|
-
checkEnd,
|
|
899
|
-
COLORS.checkbox[0], COLORS.checkbox[1], COLORS.checkbox[2],
|
|
900
|
-
false
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
// Inline elements
|
|
905
|
-
|
|
906
|
-
// Inline code: `code`
|
|
907
|
-
const codeRegex = /`([^`]+)`/g;
|
|
908
|
-
let match;
|
|
909
|
-
while ((match = codeRegex.exec(content)) !== null) {
|
|
910
|
-
editor.addOverlay(
|
|
911
|
-
bufferId,
|
|
912
|
-
"md",
|
|
913
|
-
byteStart + match.index,
|
|
914
|
-
byteStart + match.index + match[0].length,
|
|
915
|
-
COLORS.code[0], COLORS.code[1], COLORS.code[2],
|
|
916
|
-
false
|
|
917
|
-
);
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
// Bold: **text** or __text__
|
|
921
|
-
const boldRegex = /(\*\*|__)([^*_]+)\1/g;
|
|
922
|
-
while ((match = boldRegex.exec(content)) !== null) {
|
|
923
|
-
const markerLen = match[1].length;
|
|
924
|
-
const fullStart = byteStart + match.index;
|
|
925
|
-
const fullEnd = fullStart + match[0].length;
|
|
926
|
-
// Subdued markers
|
|
927
|
-
editor.addOverlay(
|
|
928
|
-
bufferId,
|
|
929
|
-
"md",
|
|
930
|
-
fullStart, fullStart + markerLen,
|
|
931
|
-
COLORS.boldMarker[0], COLORS.boldMarker[1], COLORS.boldMarker[2],
|
|
932
|
-
false, false, false
|
|
933
|
-
);
|
|
934
|
-
editor.addOverlay(
|
|
935
|
-
bufferId,
|
|
936
|
-
"md",
|
|
937
|
-
fullEnd - markerLen, fullEnd,
|
|
938
|
-
COLORS.boldMarker[0], COLORS.boldMarker[1], COLORS.boldMarker[2],
|
|
939
|
-
false, false, false
|
|
940
|
-
);
|
|
941
|
-
// Bold content
|
|
942
|
-
editor.addOverlay(
|
|
943
|
-
bufferId,
|
|
944
|
-
"md",
|
|
945
|
-
fullStart + markerLen, fullEnd - markerLen,
|
|
946
|
-
COLORS.bold[0], COLORS.bold[1], COLORS.bold[2],
|
|
947
|
-
false, true, false
|
|
948
|
-
);
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// Italic: *text* or _text_ (but not inside bold)
|
|
952
|
-
const italicRegex = /(?<!\*|\w)(\*|_)(?!\*|_)([^*_\n]+)(?<!\*|_)\1(?!\*|\w)/g;
|
|
953
|
-
while ((match = italicRegex.exec(content)) !== null) {
|
|
954
|
-
const fullStart = byteStart + match.index;
|
|
955
|
-
const fullEnd = fullStart + match[0].length;
|
|
956
|
-
// Subdued markers
|
|
957
|
-
editor.addOverlay(
|
|
958
|
-
bufferId,
|
|
959
|
-
"md",
|
|
960
|
-
fullStart, fullStart + 1,
|
|
961
|
-
COLORS.italicMarker[0], COLORS.italicMarker[1], COLORS.italicMarker[2],
|
|
962
|
-
false, false, false
|
|
963
|
-
);
|
|
964
|
-
editor.addOverlay(
|
|
965
|
-
bufferId,
|
|
966
|
-
"md",
|
|
967
|
-
fullEnd - 1, fullEnd,
|
|
968
|
-
COLORS.italicMarker[0], COLORS.italicMarker[1], COLORS.italicMarker[2],
|
|
969
|
-
false, false, false
|
|
970
|
-
);
|
|
971
|
-
// Italic content
|
|
972
|
-
editor.addOverlay(
|
|
973
|
-
bufferId,
|
|
974
|
-
"md",
|
|
975
|
-
fullStart + 1, fullEnd - 1,
|
|
976
|
-
COLORS.italic[0], COLORS.italic[1], COLORS.italic[2],
|
|
977
|
-
false, false, true
|
|
978
|
-
);
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
// Links: [text](url)
|
|
982
|
-
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
983
|
-
while ((match = linkRegex.exec(content)) !== null) {
|
|
984
|
-
const fullStart = byteStart + match.index;
|
|
985
|
-
const textStart = fullStart + 1;
|
|
986
|
-
const textEnd = textStart + match[1].length;
|
|
987
|
-
const urlStart = textEnd + 2;
|
|
988
|
-
const urlEnd = urlStart + match[2].length;
|
|
989
|
-
|
|
990
|
-
// Link text (underlined)
|
|
991
|
-
editor.addOverlay(
|
|
992
|
-
bufferId,
|
|
993
|
-
"md",
|
|
994
|
-
textStart, textEnd,
|
|
995
|
-
COLORS.link[0], COLORS.link[1], COLORS.link[2],
|
|
996
|
-
true // underline
|
|
997
|
-
);
|
|
998
|
-
// Link URL (subdued)
|
|
999
|
-
editor.addOverlay(
|
|
1000
|
-
bufferId,
|
|
1001
|
-
"md",
|
|
1002
|
-
urlStart, urlEnd,
|
|
1003
|
-
COLORS.linkUrl[0], COLORS.linkUrl[1], COLORS.linkUrl[2],
|
|
1004
|
-
false
|
|
1005
|
-
);
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
307
|
// Check if a file is a markdown file
|
|
1010
308
|
function isMarkdownFile(path: string): boolean {
|
|
1011
309
|
return path.endsWith('.md') || path.endsWith('.markdown');
|
|
@@ -1025,19 +323,6 @@ function processBuffer(bufferId: number, _splitId?: number): void {
|
|
|
1025
323
|
editor.refreshLines(bufferId);
|
|
1026
324
|
}
|
|
1027
325
|
|
|
1028
|
-
// Enable highlighting for a markdown buffer (auto on file open)
|
|
1029
|
-
function enableHighlighting(bufferId: number): void {
|
|
1030
|
-
const info = editor.getBufferInfo(bufferId);
|
|
1031
|
-
if (!info || !isMarkdownFile(info.path)) return;
|
|
1032
|
-
|
|
1033
|
-
if (!highlightingBuffers.has(bufferId)) {
|
|
1034
|
-
highlightingBuffers.add(bufferId);
|
|
1035
|
-
// Trigger a refresh so lines_changed will process visible lines
|
|
1036
|
-
editor.refreshLines(bufferId);
|
|
1037
|
-
editor.debug(`Markdown highlighting enabled for buffer ${bufferId}`);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
326
|
// Enable full compose mode for a buffer (explicit toggle)
|
|
1042
327
|
function enableMarkdownCompose(bufferId: number): void {
|
|
1043
328
|
const info = editor.getBufferInfo(bufferId);
|
|
@@ -1045,7 +330,6 @@ function enableMarkdownCompose(bufferId: number): void {
|
|
|
1045
330
|
|
|
1046
331
|
if (!composeBuffers.has(bufferId)) {
|
|
1047
332
|
composeBuffers.add(bufferId);
|
|
1048
|
-
highlightingBuffers.add(bufferId); // Also ensure highlighting is on
|
|
1049
333
|
|
|
1050
334
|
// Hide line numbers in compose mode
|
|
1051
335
|
editor.setLineNumbers(bufferId, false);
|
|
@@ -1055,7 +339,7 @@ function enableMarkdownCompose(bufferId: number): void {
|
|
|
1055
339
|
}
|
|
1056
340
|
}
|
|
1057
341
|
|
|
1058
|
-
// Disable compose mode for a buffer
|
|
342
|
+
// Disable compose mode for a buffer
|
|
1059
343
|
function disableMarkdownCompose(bufferId: number): void {
|
|
1060
344
|
if (composeBuffers.has(bufferId)) {
|
|
1061
345
|
composeBuffers.delete(bufferId);
|
|
@@ -1066,7 +350,6 @@ function disableMarkdownCompose(bufferId: number): void {
|
|
|
1066
350
|
// Clear view transform to return to normal rendering
|
|
1067
351
|
editor.clearViewTransform(bufferId);
|
|
1068
352
|
|
|
1069
|
-
// Keep highlighting on, just clear the view transform
|
|
1070
353
|
editor.refreshLines(bufferId);
|
|
1071
354
|
editor.debug(`Markdown compose disabled for buffer ${bufferId}`);
|
|
1072
355
|
}
|
|
@@ -1092,7 +375,7 @@ globalThis.markdownToggleCompose = function(): void {
|
|
|
1092
375
|
enableMarkdownCompose(bufferId);
|
|
1093
376
|
// Trigger a re-render to apply the transform
|
|
1094
377
|
editor.refreshLines(bufferId);
|
|
1095
|
-
editor.setStatus("Markdown Compose: ON (soft breaks,
|
|
378
|
+
editor.setStatus("Markdown Compose: ON (soft breaks, centered)");
|
|
1096
379
|
}
|
|
1097
380
|
};
|
|
1098
381
|
|
|
@@ -1243,7 +526,7 @@ function transformMarkdownTokens(
|
|
|
1243
526
|
}
|
|
1244
527
|
|
|
1245
528
|
// Handle view transform request - receives tokens from core for transformation
|
|
1246
|
-
// Only applies transforms when in compose mode
|
|
529
|
+
// Only applies transforms when in compose mode
|
|
1247
530
|
globalThis.onMarkdownViewTransform = function(data: {
|
|
1248
531
|
buffer_id: number;
|
|
1249
532
|
split_id: number;
|
|
@@ -1251,7 +534,7 @@ globalThis.onMarkdownViewTransform = function(data: {
|
|
|
1251
534
|
viewport_end: number;
|
|
1252
535
|
tokens: ViewTokenWire[];
|
|
1253
536
|
}): void {
|
|
1254
|
-
// Only transform when in compose mode
|
|
537
|
+
// Only transform when in compose mode
|
|
1255
538
|
if (!composeBuffers.has(data.buffer_id)) return;
|
|
1256
539
|
|
|
1257
540
|
const info = editor.getBufferInfo(data.buffer_id);
|
|
@@ -1266,18 +549,6 @@ globalThis.onMarkdownViewTransform = function(data: {
|
|
|
1266
549
|
data.viewport_start
|
|
1267
550
|
);
|
|
1268
551
|
|
|
1269
|
-
// Extract text for overlay styling
|
|
1270
|
-
const text = extractTextFromTokens(data.tokens);
|
|
1271
|
-
const parser = new MarkdownParser(text);
|
|
1272
|
-
const mdTokens = parser.parse();
|
|
1273
|
-
|
|
1274
|
-
// Adjust token offsets for viewport
|
|
1275
|
-
for (const token of mdTokens) {
|
|
1276
|
-
token.start += data.viewport_start;
|
|
1277
|
-
token.end += data.viewport_start;
|
|
1278
|
-
}
|
|
1279
|
-
applyMarkdownStyling(data.buffer_id, mdTokens);
|
|
1280
|
-
|
|
1281
552
|
// Submit the transformed tokens - keep compose_width for margins/centering
|
|
1282
553
|
const layoutHints: LayoutHints = {
|
|
1283
554
|
compose_width: config.composeWidth,
|
|
@@ -1294,103 +565,13 @@ globalThis.onMarkdownViewTransform = function(data: {
|
|
|
1294
565
|
);
|
|
1295
566
|
};
|
|
1296
567
|
|
|
1297
|
-
// Handle
|
|
1298
|
-
globalThis.onMarkdownRenderStart = function(data: { buffer_id: number }): void {
|
|
1299
|
-
// Auto-enable highlighting for markdown files on first render
|
|
1300
|
-
if (!highlightingBuffers.has(data.buffer_id)) {
|
|
1301
|
-
const info = editor.getBufferInfo(data.buffer_id);
|
|
1302
|
-
if (info && isMarkdownFile(info.path)) {
|
|
1303
|
-
highlightingBuffers.add(data.buffer_id);
|
|
1304
|
-
editor.debug(`Markdown highlighting auto-enabled for buffer ${data.buffer_id}`);
|
|
1305
|
-
} else {
|
|
1306
|
-
return;
|
|
1307
|
-
}
|
|
1308
|
-
}
|
|
1309
|
-
// Note: Don't clear overlays here - the after-insert/after-delete handlers
|
|
1310
|
-
// already clear affected ranges via clearOverlaysInRange(). Clearing all
|
|
1311
|
-
// overlays here would cause flicker since lines_changed hasn't fired yet.
|
|
1312
|
-
};
|
|
1313
|
-
|
|
1314
|
-
// Handle lines_changed - process visible lines incrementally
|
|
1315
|
-
globalThis.onMarkdownLinesChanged = function(data: {
|
|
1316
|
-
buffer_id: number;
|
|
1317
|
-
lines: Array<{
|
|
1318
|
-
line_number: number;
|
|
1319
|
-
byte_start: number;
|
|
1320
|
-
byte_end: number;
|
|
1321
|
-
content: string;
|
|
1322
|
-
}>;
|
|
1323
|
-
}): void {
|
|
1324
|
-
// Auto-enable highlighting for markdown files
|
|
1325
|
-
if (!highlightingBuffers.has(data.buffer_id)) {
|
|
1326
|
-
const info = editor.getBufferInfo(data.buffer_id);
|
|
1327
|
-
if (info && isMarkdownFile(info.path)) {
|
|
1328
|
-
highlightingBuffers.add(data.buffer_id);
|
|
1329
|
-
} else {
|
|
1330
|
-
return;
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
|
|
1334
|
-
// Process all changed lines
|
|
1335
|
-
for (const line of data.lines) {
|
|
1336
|
-
highlightLine(data.buffer_id, line.line_number, line.byte_start, line.content);
|
|
1337
|
-
}
|
|
1338
|
-
};
|
|
1339
|
-
|
|
1340
|
-
// Handle buffer activation - auto-enable highlighting for markdown files
|
|
1341
|
-
globalThis.onMarkdownBufferActivated = function(data: { buffer_id: number }): void {
|
|
1342
|
-
enableHighlighting(data.buffer_id);
|
|
1343
|
-
};
|
|
1344
|
-
|
|
1345
|
-
// Handle content changes - clear affected overlays for efficient updates
|
|
1346
|
-
globalThis.onMarkdownAfterInsert = function(data: {
|
|
1347
|
-
buffer_id: number;
|
|
1348
|
-
position: number;
|
|
1349
|
-
text: string;
|
|
1350
|
-
affected_start: number;
|
|
1351
|
-
affected_end: number;
|
|
1352
|
-
}): void {
|
|
1353
|
-
if (!highlightingBuffers.has(data.buffer_id)) return;
|
|
1354
|
-
|
|
1355
|
-
// Clear only overlays in the affected byte range
|
|
1356
|
-
// These overlays may now span incorrect content after the insertion
|
|
1357
|
-
// The affected lines will be re-processed via lines_changed with correct content
|
|
1358
|
-
editor.clearOverlaysInRange(data.buffer_id, data.affected_start, data.affected_end);
|
|
1359
|
-
};
|
|
1360
|
-
|
|
1361
|
-
globalThis.onMarkdownAfterDelete = function(data: {
|
|
1362
|
-
buffer_id: number;
|
|
1363
|
-
start: number;
|
|
1364
|
-
end: number;
|
|
1365
|
-
deleted_text: string;
|
|
1366
|
-
affected_start: number;
|
|
1367
|
-
deleted_len: number;
|
|
1368
|
-
}): void {
|
|
1369
|
-
if (!highlightingBuffers.has(data.buffer_id)) return;
|
|
1370
|
-
|
|
1371
|
-
// Clear overlays that overlapped with the deleted range
|
|
1372
|
-
// Overlays entirely within the deleted range are already gone (their markers were deleted)
|
|
1373
|
-
// But overlays spanning the deletion boundary may now be incorrect
|
|
1374
|
-
// Use a slightly expanded range to catch boundary cases
|
|
1375
|
-
const clearStart = data.affected_start > 0 ? data.affected_start - 1 : 0;
|
|
1376
|
-
const clearEnd = data.affected_start + data.deleted_len + 1;
|
|
1377
|
-
editor.clearOverlaysInRange(data.buffer_id, clearStart, clearEnd);
|
|
1378
|
-
};
|
|
1379
|
-
|
|
1380
|
-
// Handle buffer close events
|
|
568
|
+
// Handle buffer close events - clean up compose mode tracking
|
|
1381
569
|
globalThis.onMarkdownBufferClosed = function(data: { buffer_id: number }): void {
|
|
1382
|
-
highlightingBuffers.delete(data.buffer_id);
|
|
1383
570
|
composeBuffers.delete(data.buffer_id);
|
|
1384
|
-
dirtyBuffers.delete(data.buffer_id);
|
|
1385
571
|
};
|
|
1386
572
|
|
|
1387
573
|
// Register hooks
|
|
1388
574
|
editor.on("view_transform_request", "onMarkdownViewTransform");
|
|
1389
|
-
editor.on("render_start", "onMarkdownRenderStart");
|
|
1390
|
-
editor.on("lines_changed", "onMarkdownLinesChanged");
|
|
1391
|
-
editor.on("buffer_activated", "onMarkdownBufferActivated");
|
|
1392
|
-
editor.on("after-insert", "onMarkdownAfterInsert");
|
|
1393
|
-
editor.on("after-delete", "onMarkdownAfterDelete");
|
|
1394
575
|
editor.on("buffer_closed", "onMarkdownBufferClosed");
|
|
1395
576
|
editor.on("prompt_confirmed", "onMarkdownComposeWidthConfirmed");
|
|
1396
577
|
|
|
@@ -1430,7 +611,7 @@ globalThis.onMarkdownComposeWidthConfirmed = function(args: {
|
|
|
1430
611
|
// Register commands
|
|
1431
612
|
editor.registerCommand(
|
|
1432
613
|
"Markdown: Toggle Compose",
|
|
1433
|
-
"Toggle
|
|
614
|
+
"Toggle compose mode (soft wrapping, centered margins)",
|
|
1434
615
|
"markdownToggleCompose",
|
|
1435
616
|
"normal"
|
|
1436
617
|
);
|