Skip to content

Commit cfb59e5

Browse files
committed
chore: ace to codemirror migration
- a minimal function editor - color view - modelist and supported mode stuff - emmet support - extension for editor to take neccesary stuff and not disturb scrolling and editor behaviour
1 parent fe77346 commit cfb59e5

File tree

17 files changed

+1119
-372
lines changed

17 files changed

+1119
-372
lines changed

package-lock.json

Lines changed: 150 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,30 @@
9494
"webpack-cli": "^6.0.1"
9595
},
9696
"dependencies": {
97+
"@codemirror/autocomplete": "^6.18.6",
98+
"@codemirror/commands": "^6.8.1",
99+
"@codemirror/lang-cpp": "^6.0.3",
100+
"@codemirror/lang-css": "^6.3.1",
101+
"@codemirror/lang-go": "^6.0.1",
102+
"@codemirror/lang-html": "^6.4.9",
103+
"@codemirror/lang-java": "^6.0.2",
104+
"@codemirror/lang-javascript": "^6.2.4",
105+
"@codemirror/lang-json": "^6.0.2",
106+
"@codemirror/lang-markdown": "^6.3.4",
107+
"@codemirror/lang-php": "^6.0.2",
108+
"@codemirror/lang-python": "^6.2.1",
109+
"@codemirror/lang-rust": "^6.0.2",
110+
"@codemirror/lang-sass": "^6.0.2",
111+
"@codemirror/lang-vue": "^0.1.3",
112+
"@codemirror/lang-xml": "^6.1.0",
113+
"@codemirror/lang-yaml": "^6.1.2",
114+
"@codemirror/language": "^6.11.3",
115+
"@codemirror/search": "^6.5.11",
116+
"@codemirror/state": "^6.5.2",
117+
"@codemirror/theme-one-dark": "^6.1.3",
118+
"@codemirror/view": "^6.38.1",
97119
"@deadlyjack/ajax": "^1.2.6",
120+
"@emmetio/codemirror6-plugin": "^0.4.0",
98121
"@ungap/custom-elements": "^1.3.0",
99122
"@xterm/addon-attach": "^0.11.0",
100123
"@xterm/addon-fit": "^0.10.0",
@@ -105,6 +128,7 @@
105128
"@xterm/addon-webgl": "^0.18.0",
106129
"@xterm/xterm": "^5.5.0",
107130
"autosize": "^6.0.1",
131+
"codemirror": "^6.0.2",
108132
"cordova": "12.0.0",
109133
"core-js": "^3.45.0",
110134
"crypto-js": "^4.2.0",

src/codemirror/colorView.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import {
2+
Decoration,
3+
EditorView,
4+
ViewPlugin,
5+
WidgetType,
6+
} from "@codemirror/view";
7+
import pickColor from "dialogs/color";
8+
import color from "utils/color";
9+
import { colorRegex, HEX } from "utils/color/regex";
10+
11+
// WeakMap to carry state from widget DOM back into handler
12+
const colorState = new WeakMap();
13+
14+
const HEX_RE = new RegExp(HEX, "gi");
15+
16+
const RGBG = new RegExp(colorRegex.anyGlobal);
17+
18+
const enumColorType = { hex: "hex", rgb: "rgb", hsl: "hsl", named: "named" };
19+
20+
class ColorWidget extends WidgetType {
21+
constructor({ color, colorRaw, ...state }) {
22+
super();
23+
this.state = state; // from, to, colorType, alpha
24+
this.color = color; // hex for input value
25+
this.colorRaw = colorRaw; // original css color string
26+
}
27+
eq(other) {
28+
return (
29+
other.state.colorType === this.state.colorType &&
30+
other.color === this.color &&
31+
other.state.from === this.state.from &&
32+
other.state.to === this.state.to &&
33+
(other.state.alpha || "") === (this.state.alpha || "")
34+
);
35+
}
36+
toDOM() {
37+
const wrapper = document.createElement("span");
38+
wrapper.className = "cm-color-chip";
39+
wrapper.style.display = "inline-block";
40+
wrapper.style.width = "0.9em";
41+
wrapper.style.height = "0.9em";
42+
wrapper.style.borderRadius = "2px";
43+
wrapper.style.verticalAlign = "middle";
44+
wrapper.style.margin = "0 2px";
45+
wrapper.style.boxSizing = "border-box";
46+
wrapper.style.border = "1px solid rgba(0,0,0,0.2)";
47+
wrapper.style.backgroundColor = this.colorRaw;
48+
wrapper.dataset["color"] = this.color;
49+
wrapper.dataset["colorraw"] = this.colorRaw;
50+
wrapper.style.cursor = "pointer";
51+
colorState.set(wrapper, this.state);
52+
return wrapper;
53+
}
54+
ignoreEvent() {
55+
return false;
56+
}
57+
}
58+
59+
function colorDecorations(view) {
60+
const deco = [];
61+
const ranges = view.visibleRanges;
62+
for (const { from, to } of ranges) {
63+
const text = view.state.doc.sliceString(from, to);
64+
// Any color using global matcher from utils (captures named/rgb/rgba/hsl/hsla/hex)
65+
RGBG.lastIndex = 0;
66+
for (let m; (m = RGBG.exec(text)); ) {
67+
const raw = m[2];
68+
const start = from + m.index + m[1].length;
69+
const end = start + raw.length;
70+
const c = color(raw);
71+
const colorHex = c.hex.toString(false);
72+
deco.push(
73+
Decoration.widget({
74+
widget: new ColorWidget({
75+
from: start,
76+
to: end,
77+
color: colorHex,
78+
colorRaw: raw,
79+
colorType: enumColorType.named,
80+
}),
81+
side: -1,
82+
}).range(start),
83+
);
84+
}
85+
}
86+
87+
return Decoration.set(deco, { sort: true });
88+
}
89+
90+
export const colorView = (showPicker = true) =>
91+
ViewPlugin.fromClass(
92+
class ColorViewPlugin {
93+
constructor(view) {
94+
this.decorations = colorDecorations(view);
95+
}
96+
update(update) {
97+
if (update.docChanged || update.viewportChanged) {
98+
this.decorations = colorDecorations(update.view);
99+
}
100+
const readOnly = update.view.contentDOM.ariaReadOnly === "true";
101+
const editable = update.view.contentDOM.contentEditable === "true";
102+
const canBeEdited = readOnly === false && editable;
103+
this.changePicker(update.view, canBeEdited);
104+
}
105+
changePicker(view, canBeEdited) {
106+
const doms = view.contentDOM.querySelectorAll("input[type=color]");
107+
doms.forEach((inp) => {
108+
if (!showPicker) {
109+
inp.setAttribute("disabled", "");
110+
} else {
111+
canBeEdited
112+
? inp.removeAttribute("disabled")
113+
: inp.setAttribute("disabled", "");
114+
}
115+
});
116+
}
117+
},
118+
{
119+
decorations: (v) => v.decorations,
120+
eventHandlers: {
121+
click: async (e, view) => {
122+
const target = e.target;
123+
const chip = target?.closest?.(".cm-color-chip");
124+
if (!chip) return false;
125+
// Respect read-only and setting toggle
126+
const readOnly = view.contentDOM.ariaReadOnly === "true";
127+
const editable = view.contentDOM.contentEditable === "true";
128+
const canBeEdited = !readOnly && editable;
129+
if (!canBeEdited) return true;
130+
const data = colorState.get(chip);
131+
if (!data) return false;
132+
try {
133+
const picked = await pickColor(
134+
chip.dataset.colorraw || chip.dataset.color,
135+
);
136+
if (!picked) return true;
137+
view.dispatch({
138+
changes: { from: data.from, to: data.to, insert: picked },
139+
});
140+
} catch {
141+
/* ignore */
142+
}
143+
return true;
144+
},
145+
},
146+
},
147+
);
148+
149+
export default colorView;

0 commit comments

Comments
 (0)