Skip to content

Commit ca01316

Browse files
committed
feat: show gutter marker next to line under pointer
1 parent 16a75df commit ca01316

File tree

3 files changed

+148
-1
lines changed

3 files changed

+148
-1
lines changed

src/main.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { type Extension } from "@codemirror/state";
12
import {
23
MarkdownView,
34
Notice,
@@ -69,6 +70,7 @@ import { createGetTasksApi } from "./tasks-plugin";
6970
import type { ObsidianContext, OnUpdateFn, PointerDateTime } from "./types";
7071
import { askForConfirmation } from "./ui/confirmation-modal";
7172
import { createEditorMenuCallback } from "./ui/editor-menu";
73+
import { hoverPlugin } from "./ui/gutter-plugin";
7274
import { useDateRanges } from "./ui/hooks/use-date-ranges";
7375
import { useDebounceWithDelay } from "./ui/hooks/use-debounce-with-delay";
7476
import { mountStatusBarWidget } from "./ui/hooks/use-status-bar-widget";
@@ -94,6 +96,7 @@ export default class DayPlanner extends Plugin {
9496
private sTaskEditor!: STaskEditor;
9597
private vaultFacade!: VaultFacade;
9698
private transactionWriter!: TransactionWriter;
99+
private extensions: Extension[] = [];
97100

98101
async onload() {
99102
const initialPluginData: PluginData = {
@@ -183,6 +186,13 @@ export default class DayPlanner extends Plugin {
183186

184187
await this.handleNewPluginVersion();
185188
await this.initTimelineLeafSilently();
189+
190+
this.registerEditorExtension([
191+
hoverPlugin({
192+
onpointerup: () => {},
193+
markerPredicate: () => true,
194+
}),
195+
]);
186196
}
187197

188198
async onunload() {

src/ui/gutter-plugin.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { Prec, StateEffect, StateField } from "@codemirror/state";
2+
import { EditorView, gutter, GutterMarker, ViewPlugin } from "@codemirror/view";
3+
import { getIcon } from "obsidian";
4+
import { isNotVoid } from "typed-assert";
5+
6+
class ClockControlMarker extends GutterMarker {
7+
toDOM() {
8+
const icon = getIcon("play");
9+
10+
isNotVoid(icon);
11+
12+
return icon;
13+
}
14+
}
15+
16+
export function createClockControlExtension(props: {
17+
onpointerup: (event: Event, line: number) => void;
18+
}) {
19+
const { onpointerup } = props;
20+
21+
const marker = new ClockControlMarker();
22+
23+
return Prec.lowest(
24+
gutter({
25+
class: "cm-planner-clock-gutter",
26+
lineMarker(view, line) {
27+
if (
28+
view.state.doc.lineAt(line.from).number ===
29+
view.state.field(hoveredLineNumber)
30+
) {
31+
return marker;
32+
}
33+
34+
return null;
35+
},
36+
lineMarkerChange(update) {
37+
return (
38+
update.state.field(hoveredLineNumber) !==
39+
update.startState.field(hoveredLineNumber)
40+
);
41+
},
42+
initialSpacer() {
43+
return marker;
44+
},
45+
domEventHandlers: {
46+
pointerup: (view, line, event) => {
47+
onpointerup(event, view.state.doc.lineAt(line.from).number);
48+
49+
return true;
50+
},
51+
},
52+
}),
53+
);
54+
}
55+
56+
const setHoveredLineNumber = StateEffect.define<number | null>({
57+
map(line) {
58+
return line;
59+
},
60+
});
61+
62+
const hoveredLineNumber = StateField.define<number | null>({
63+
create() {
64+
return null;
65+
},
66+
update(previous, transaction) {
67+
return (
68+
transaction.effects.find((it) => it.is(setHoveredLineNumber))?.value ??
69+
previous
70+
);
71+
},
72+
});
73+
74+
function createLineNumberPlugin(props: {
75+
markerPredicate: (line: number | null) => boolean;
76+
}) {
77+
const { markerPredicate } = props;
78+
79+
return ViewPlugin.fromClass(
80+
class {
81+
private readonly markerPredicate = markerPredicate;
82+
83+
constructor(readonly view: EditorView) {}
84+
85+
setHoveredLineNumber(lineNumber: number | null) {
86+
if (
87+
this.view.state.field(hoveredLineNumber) !== lineNumber &&
88+
this.markerPredicate(lineNumber)
89+
) {
90+
this.view.dispatch({
91+
effects: setHoveredLineNumber.of(lineNumber),
92+
});
93+
}
94+
}
95+
},
96+
{
97+
eventObservers: {
98+
mouseover(event, view) {
99+
const posAtCoords = view.posAtCoords({
100+
x: event.clientX,
101+
y: event.clientY,
102+
});
103+
104+
const lineAtCursor = posAtCoords
105+
? view.state.doc.lineAt(posAtCoords).number
106+
: null;
107+
108+
this.setHoveredLineNumber(lineAtCursor);
109+
},
110+
},
111+
},
112+
);
113+
}
114+
115+
export function hoverPlugin(props: {
116+
onpointerup: (event: Event, line: number) => void;
117+
markerPredicate: (line: number | null) => boolean;
118+
}) {
119+
const { onpointerup, markerPredicate } = props;
120+
121+
return [
122+
createClockControlExtension({ onpointerup }),
123+
createLineNumberPlugin({ markerPredicate }),
124+
hoveredLineNumber,
125+
];
126+
}

vite.config.mts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,18 @@ export default defineConfig((env) => ({
7373
return assetInfo.name;
7474
},
7575
},
76-
external: ["obsidian", "electron"],
76+
external: [
77+
"obsidian",
78+
"electron",
79+
"@codemirror/autocomplete",
80+
"@codemirror/collab",
81+
"@codemirror/commands",
82+
"@codemirror/language",
83+
"@codemirror/lint",
84+
"@codemirror/search",
85+
"@codemirror/state",
86+
"@codemirror/view",
87+
],
7788
},
7889
},
7990
test: {

0 commit comments

Comments
 (0)