import { syntaxTree } from '@codemirror/language';
import type { Extension, Range } from '@codemirror/state';
import type { DecorationSet, ViewUpdate } from '@codemirror/view';
import { ViewPlugin, EditorView, Decoration, WidgetType } from '@codemirror/view';
const pathStr = ``;
export interface HyperLinkState {
from: number;
to: number;
url: string;
}
class HyperLink extends WidgetType {
protected readonly state: HyperLinkState;
constructor(state: HyperLinkState) {
super();
this.state = state;
}
override eq(other: HyperLink) {
return (
this.state.url === other.state.url &&
this.state.to === other.state.to &&
this.state.from === other.state.from
);
}
toDOM() {
const wrapper = document.createElement('a');
wrapper.href = this.state.url;
wrapper.target = '__blank';
wrapper.innerHTML = pathStr;
wrapper.className = 'cm-hyper-link-icon';
return wrapper;
}
override ignoreEvent() {
return false;
}
}
function hyperLinkDecorations(view: EditorView) {
const widgets: Range[] = [];
for (const range of view.visibleRanges) {
syntaxTree(view.state).iterate({
from: range.from,
to: range.to,
enter: ({ type, from, to }) => {
const callExp: string = view.state.doc.sliceString(from, to);
if (type.name === 'URL') {
const widget = Decoration.widget({
widget: new HyperLink({
from,
to,
url: callExp,
}),
side: 1,
});
widgets.push(widget.range(to));
}
},
});
}
return Decoration.set(widgets);
}
export function hyperLinkExtension() {
return ViewPlugin.fromClass(
class HyperLinkView {
decorations: DecorationSet;
constructor(view: EditorView) {
this.decorations = hyperLinkDecorations(view);
}
update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged) {
this.decorations = hyperLinkDecorations(update.view);
}
}
},
{
decorations: (v) => v.decorations,
},
);
}
export const hyperLinkStyle = EditorView.baseTheme({
'.cm-hyper-link-icon': {
display: 'inline-block',
verticalAlign: 'middle',
marginLeft: '0.2ch',
},
'.cm-hyper-link-icon svg': {
display: 'block',
},
});
export const hyperLink: Extension = [hyperLinkExtension(), hyperLinkStyle];