Skip to content

Commit b3c028c

Browse files
committed
Canvas support and other improvements
1 parent 4185972 commit b3c028c

File tree

8 files changed

+556
-361
lines changed

8 files changed

+556
-361
lines changed

AutoDirPlugin.ts

Lines changed: 182 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -8,197 +8,210 @@ import {
88
} from "@codemirror/view";
99
import { RangeSetBuilder, Text } from "@codemirror/state";
1010
import { detectDirection } from './globals';
11+
import RtlPlugin from './main';
12+
import { editorInfoField, MarkdownView } from 'obsidian';
1113

1214
type Region = {from: number; to: number;};
1315
type DecorationRegion = Region & {dec: Decoration};
1416

15-
class AutoDirectionPlugin implements PluginValue {
16-
decorations: DecorationSet;
17-
// A cache mechanism for regions, so we don't need to calculate the decoration for a line if it doesn't
18-
// change.
19-
decorationRegions: DecorationRegion[] = [];
20-
active = false;
21-
22-
rtlDec = Decoration.line({
23-
attributes: { dir: 'rtl' },
24-
});
25-
ltrDec = Decoration.line({
26-
attributes: { dir: 'ltr' },
27-
});
28-
emptyDirDec = Decoration.line({
29-
attributes: { dir: "" },
30-
});
31-
autoDec = Decoration.line({
32-
attributes: { dir: 'auto' },
33-
});
34-
35-
constructor(_view: EditorView) {
36-
this.decorations = this.buildDecorations();
37-
}
38-
39-
update(vu: ViewUpdate) {
40-
if (vu.viewportChanged || vu.docChanged) {
41-
const regions: Region[] = [];
42-
if (vu.docChanged) {
43-
// Trying to calculate the regions that have been changed and also modifying
44-
// (shift) other regions regarding to that change.
45-
// So for example if we have `First line\nSecond line\Test` any insertion or
46-
// deletion on the second line will result in a shift on the next lines (here third
47-
// line) and will add the second line region to regions array so we recalculate the direction.
48-
vu.changes.iterChanges((fromA, toA, fromB, toB) => {
49-
const shift = (toB - fromB) - (toA - fromA);
50-
this.shiftDecorationRegions(shift < 0 ? toB : toA, shift);
51-
52-
regions.push(...this.getLineRegions(vu.state.doc, fromB, toB));
53-
});
54-
}
17+
export interface AutoDirectionPlugin extends PluginValue {
18+
setActive(active: boolean, view: EditorView): void;
19+
}
5520

56-
this.updateEx(vu.view, regions);
57-
}
58-
}
59-
60-
destroy() {}
61-
62-
setActive(active: boolean, view: EditorView) {
63-
const forceUpdate = this.active !== active;
64-
this.active = active;
65-
this.decorations = this.buildDecorations();
66-
67-
if (forceUpdate) {
68-
this.updateEx(view);
69-
}
70-
view.dispatch();
71-
}
72-
73-
// Calculate the line decoration (rtl|ltr|auto|none) for each line that has an intersection with
74-
// given regions. Note that a single region could include or intersect with multiple lines.
75-
updateEx(view: EditorView, regions: Region[] = []) {
76-
// If regions is empty recalculate the decoration for all lines in the viewport
77-
if (regions.length === 0) {
78-
const {from, to} = view.viewport;
79-
regions = this.getLineRegions(view.state.doc, from, to);
80-
}
81-
82-
for (const { from, to } of regions) {
83-
for (let pos = from; pos <= to; ) {
84-
const line = view.state.doc.lineAt(pos);
85-
86-
let dec = this.emptyDirDec;
87-
if (this.active) {
88-
const s = view.state.doc.sliceString(line.from, line.to);
89-
const d = this.detectDecoration(s);
90-
// If we couldn't find a proper decoration, use the line before the decoration
91-
dec = d ? d : this.lineBeforeDecoration(line.from);
21+
export function getAutoDirectionPlugin(rtlPlugin: RtlPlugin) {
22+
return ViewPlugin.fromClass(
23+
class implements AutoDirectionPlugin {
24+
rtlPlugin: RtlPlugin;
25+
view: EditorView;
26+
decorations: DecorationSet;
27+
// A cache mechanism for regions, so we don't need to calculate the decoration for a line if it doesn't
28+
// change.
29+
decorationRegions: DecorationRegion[] = [];
30+
active = false;
31+
32+
rtlDec = Decoration.line({
33+
attributes: { dir: 'rtl' },
34+
});
35+
ltrDec = Decoration.line({
36+
attributes: { dir: 'ltr' },
37+
});
38+
emptyDirDec = Decoration.line({
39+
attributes: { dir: "" },
40+
});
41+
autoDec = Decoration.line({
42+
attributes: { dir: 'auto' },
43+
});
44+
45+
constructor(view: EditorView) {
46+
this.decorations = this.buildDecorations();
47+
this.rtlPlugin = rtlPlugin;
48+
this.view = view;
49+
const editorInfo = this.view.state.field(editorInfoField);
50+
if (editorInfo instanceof MarkdownView) {
51+
this.rtlPlugin.adjustDirectionToView(editorInfo, this);
9252
}
53+
this.rtlPlugin.handleIframeEditor(this.view.dom, this.view, editorInfo.file, this);
54+
}
9355

94-
this.addDecorationRegion({from: line.from, to: line.to, dec});
95-
// Advance to the next line
96-
pos = line.to + 1;
56+
update(vu: ViewUpdate) {
57+
if (vu.viewportChanged || vu.docChanged) {
58+
const regions: Region[] = [];
59+
if (vu.docChanged) {
60+
// Trying to calculate the regions that have been changed and also modifying
61+
// (shift) other regions according to that change.
62+
// So for example if we have `First line\nSecond line\Test` any insertion or
63+
// deletion on the second line will result in a shift on the next lines (here third
64+
// line) and will add the second line region to regions array so we recalculate the direction.
65+
vu.changes.iterChanges((fromA, toA, fromB, toB) => {
66+
const shift = (toB - fromB) - (toA - fromA);
67+
this.shiftDecorationRegions(shift < 0 ? toB : toA, shift);
68+
69+
regions.push(...this.getLineRegions(vu.state.doc, fromB, toB));
70+
});
71+
}
72+
73+
this.updateEx(vu.view, regions);
74+
}
9775
}
98-
}
99-
100-
this.decorations = this.buildDecorations();
101-
}
102-
103-
buildDecorations(): DecorationSet {
104-
const builder = new RangeSetBuilder<Decoration>();
105-
for (const dr of this.decorationRegions) {
106-
builder.add(dr.from, dr.from, dr.dec);
107-
}
108-
109-
return builder.finish();
110-
}
111-
112-
// Adding a decoration region while keeping the decoration regions in order based on their
113-
// start ('from' property). This will either replace a decoration region or add one in the middle
114-
// or append to end of the decoration regions.
115-
addDecorationRegion(dr: DecorationRegion) {
116-
for (let i = 0; i < this.decorationRegions.length; i++) {
117-
if (this.decorationRegions[i].from < dr.from) {
118-
continue;
76+
77+
destroy() {}
78+
79+
setActive(active: boolean, view: EditorView) {
80+
const forceUpdate = this.active !== active;
81+
this.active = active;
82+
this.decorations = this.buildDecorations();
83+
84+
if (forceUpdate) {
85+
this.updateEx(view);
86+
}
11987
}
12088

121-
if (this.decorationRegions[i].from === dr.from) {
122-
this.decorationRegions[i] = dr;
123-
} else if (this.decorationRegions[i].from > dr.from) {
124-
this.decorationRegions.splice(i, 0, dr);
89+
// Calculate the line decoration (rtl|ltr|auto|none) for each line that has an intersection with
90+
// given regions. Note that a single region could include or intersect with multiple lines.
91+
updateEx(view: EditorView, regions: Region[] = []) {
92+
// If regions is empty recalculate the decoration for all lines in the viewport
93+
if (regions.length === 0) {
94+
const {from, to} = view.viewport;
95+
regions = this.getLineRegions(view.state.doc, from, to);
96+
}
97+
98+
for (const { from, to } of regions) {
99+
for (let pos = from; pos <= to; ) {
100+
const line = view.state.doc.lineAt(pos);
101+
102+
let dec = this.emptyDirDec;
103+
if (this.active) {
104+
const s = view.state.doc.sliceString(line.from, line.to);
105+
const d = this.detectDecoration(s);
106+
// If we couldn't find a proper decoration, use the line before the decoration
107+
dec = d ? d : this.lineBeforeDecoration(line.from);
108+
}
109+
110+
this.addDecorationRegion({from: line.from, to: line.to, dec});
111+
// Advance to the next line
112+
pos = line.to + 1;
113+
}
114+
}
115+
116+
this.decorations = this.buildDecorations();
125117
}
126118

127-
return;
128-
}
119+
buildDecorations(): DecorationSet {
120+
const builder = new RangeSetBuilder<Decoration>();
121+
for (const dr of this.decorationRegions) {
122+
builder.add(dr.from, dr.from, dr.dec);
123+
}
129124

130-
this.decorationRegions.push(dr);
131-
}
125+
return builder.finish();
126+
}
132127

133-
// Shifting every decorationRegions region which their start is after the 'from' variable based
134-
// on the given amount.
135-
shiftDecorationRegions(from: number, amount: number) {
136-
if (amount === 0) {
137-
return;
138-
}
128+
// Adding a decoration region while keeping the decoration regions in order based on their
129+
// start ('from' property). This will either replace a decoration region or add one in the middle
130+
// or append to end of the decoration regions.
131+
addDecorationRegion(dr: DecorationRegion) {
132+
for (let i = 0; i < this.decorationRegions.length; i++) {
133+
if (this.decorationRegions[i].from < dr.from) {
134+
continue;
135+
}
136+
137+
if (this.decorationRegions[i].from === dr.from) {
138+
this.decorationRegions[i] = dr;
139+
} else if (this.decorationRegions[i].from > dr.from) {
140+
this.decorationRegions.splice(i, 0, dr);
141+
}
142+
143+
return;
144+
}
139145

140-
for (let i = 0; i < this.decorationRegions.length; i++) {
141-
if (this.decorationRegions[i].from < from) {
142-
continue;
146+
this.decorationRegions.push(dr);
143147
}
144148

145-
this.decorationRegions[i].from += amount;
146-
this.decorationRegions[i].to += amount;
149+
// Shifting every decorationRegions region which their start is after the 'from' variable based
150+
// on the given amount.
151+
shiftDecorationRegions(from: number, amount: number) {
152+
if (amount === 0) {
153+
return;
154+
}
147155

148-
// The shift amount could be negative (on deletion). If after shifting with a negative
149-
// amount the region gets below the 'from' variable we will remove the decoration region
150-
// as the decoration will get calculated again.
151-
if (this.decorationRegions[i].from <= from) {
152-
this.decorationRegions.splice(i, 1);
153-
i--;
156+
for (let i = 0; i < this.decorationRegions.length; i++) {
157+
if (this.decorationRegions[i].from < from) {
158+
continue;
159+
}
160+
161+
this.decorationRegions[i].from += amount;
162+
this.decorationRegions[i].to += amount;
163+
164+
// The shift amount could be negative (on deletion). If after shifting with a negative
165+
// amount the region gets below the 'from' variable we will remove the decoration region
166+
// as the decoration will get calculated again.
167+
if (this.decorationRegions[i].from <= from) {
168+
this.decorationRegions.splice(i, 1);
169+
i--;
170+
}
171+
}
154172
}
155-
}
156-
}
157-
158-
detectDecoration(s: string): Decoration|null {
159-
// Replacing so we don't get the 'x' character which is used to show a checked checkbox in
160-
// markdown as a LTR direction indicator.
161-
const direction = detectDirection(s.replace('- [x]', ''));
162-
switch (direction) {
163-
case 'rtl':
164-
return this.rtlDec;
165-
case 'ltr':
166-
return this.ltrDec;
167-
}
168-
169-
return null;
170-
}
171-
172-
lineBeforeDecoration(from: number, def=this.ltrDec): Decoration {
173-
const l = this.decorationRegions.length;
174-
// If 'from' is out of decoration regions scope use the last one.
175-
if (l !== 0 && from > this.decorationRegions[l-1].from) {
176-
return this.decorationRegions[l-1].dec;
177-
}
178-
179-
for (let i = 0; i < l; i++) {
180-
if (i !== 0 && this.decorationRegions[i].from >= from) {
181-
return this.decorationRegions[i-1].dec;
173+
174+
detectDecoration(s: string): Decoration | null {
175+
// Replacing so we don't get the 'x' character which is used to show a checked checkbox in
176+
// markdown as a LTR direction indicator.
177+
const direction = detectDirection(s.replace('- [x]', ''));
178+
switch (direction) {
179+
case 'rtl':
180+
return this.rtlDec;
181+
case 'ltr':
182+
return this.ltrDec;
183+
}
184+
185+
return null;
182186
}
183-
}
184187

185-
return def;
186-
}
188+
lineBeforeDecoration(from: number, def=this.ltrDec): Decoration {
189+
const l = this.decorationRegions.length;
190+
// If 'from' is out of decoration regions scope use the last one.
191+
if (l !== 0 && from > this.decorationRegions[l-1].from) {
192+
return this.decorationRegions[l-1].dec;
193+
}
194+
195+
for (let i = 0; i < l; i++) {
196+
if (i !== 0 && this.decorationRegions[i].from >= from) {
197+
return this.decorationRegions[i-1].dec;
198+
}
199+
}
187200

188-
// Get all lines region between 'from' and 'to'
189-
getLineRegions(doc: Text, from: number, to: number): Region[] {
190-
const regions: Region[] = [];
191-
for (let i = from; i <= to; i++) {
192-
const l = doc.lineAt(i);
193-
i = l.to;
201+
return def;
202+
}
194203

195-
regions.push({from: l.from, to: l.to});
196-
}
204+
// Get all lines region between 'from' and 'to'
205+
getLineRegions(doc: Text, from: number, to: number): Region[] {
206+
const regions: Region[] = [];
207+
for (let i = from; i <= to; i++) {
208+
const l = doc.lineAt(i);
209+
i = l.to;
197210

198-
return regions;
199-
}
200-
}
211+
regions.push({from: l.from, to: l.to});
212+
}
201213

202-
export const autoDirectionPlugin = ViewPlugin.fromClass(AutoDirectionPlugin, {
203-
decorations: (v) => v.decorations,
204-
});
214+
return regions;
215+
}
216+
}, {decorations: (v) => v.decorations});
217+
}

0 commit comments

Comments
 (0)