Add API for propagating cursor locations #61
5 changed files with 87 additions and 9 deletions
Send cursors instantly
commit
f4c77ddd25
|
|
@ -48,6 +48,10 @@ impl Cursors {
|
||||||
device_id: device_id.to_string(),
|
device_id: device_id.to_string(),
|
||||||
cursors: document_to_cursors,
|
cursors: document_to_cursors,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
drop(vault_to_cursors); // Explicitly drop the lock before broadcasting to avoid deadlock
|
||||||
|
|
||||||
|
self.broadcast_cursors().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_cursors(&self, vault_id: &VaultId) -> Vec<ClientCursors> {
|
pub async fn get_cursors(&self, vault_id: &VaultId) -> Vec<ClientCursors> {
|
||||||
|
|
@ -73,7 +77,6 @@ impl Cursors {
|
||||||
async fn run_backround_task(&self) {
|
async fn run_backround_task(&self) {
|
||||||
loop {
|
loop {
|
||||||
self.remove_expired_cursors().await;
|
self.remove_expired_cursors().await;
|
||||||
self.broadcast_cursors().await;
|
|
||||||
tokio::time::sleep(self.config.cursor_broadcast_interval).await;
|
tokio::time::sleep(self.config.cursor_broadcast_interval).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
} from "sync-client";
|
} from "sync-client";
|
||||||
import { lineAndColumnToPosition } from "./utils/line-and-column-to-position";
|
import { lineAndColumnToPosition } from "./utils/line-and-column-to-position";
|
||||||
import { positionToLineAndColumn } from "./utils/position-to-line-and-column";
|
import { positionToLineAndColumn } from "./utils/position-to-line-and-column";
|
||||||
import { getCursorsFromEditor } from "./utils/get-cursors-from-editor";
|
import { getCursorsFromEditor } from "./views/cursors/get-cursors-from-editor";
|
||||||
|
|
||||||
export class ObsidianFileSystemOperations implements FileSystemOperations {
|
export class ObsidianFileSystemOperations implements FileSystemOperations {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,36 @@
|
||||||
import type {
|
import type {
|
||||||
Editor,
|
Editor,
|
||||||
|
EventRef,
|
||||||
MarkdownFileInfo,
|
MarkdownFileInfo,
|
||||||
MarkdownView,
|
|
||||||
TAbstractFile,
|
TAbstractFile,
|
||||||
|
Workspace,
|
||||||
WorkspaceLeaf
|
WorkspaceLeaf
|
||||||
} from "obsidian";
|
} from "obsidian";
|
||||||
|
import { MarkdownView } from "obsidian";
|
||||||
import { Platform, Plugin, TFile } from "obsidian";
|
import { Platform, Plugin, TFile } from "obsidian";
|
||||||
import "../manifest.json";
|
import "../manifest.json";
|
||||||
import { HistoryView } from "./views/history/history-view";
|
import { HistoryView } from "./views/history/history-view";
|
||||||
import { StatusBar } from "./views/status-bar/status-bar";
|
import { StatusBar } from "./views/status-bar/status-bar";
|
||||||
import { LogsView } from "./views/logs/logs-view";
|
import { LogsView } from "./views/logs/logs-view";
|
||||||
import { StatusDescription } from "./views/status-description/status-description";
|
import { StatusDescription } from "./views/status-description/status-description";
|
||||||
|
import type { CursorSpan, RelativePath } from "sync-client";
|
||||||
import { SyncClient, rateLimit, DEFAULT_SETTINGS } from "sync-client";
|
import { SyncClient, rateLimit, DEFAULT_SETTINGS } from "sync-client";
|
||||||
import { ObsidianFileSystemOperations } from "./obsidian-file-system";
|
import { ObsidianFileSystemOperations } from "./obsidian-file-system";
|
||||||
import { SyncSettingsTab } from "./views/settings/settings-tab";
|
import { SyncSettingsTab } from "./views/settings/settings-tab";
|
||||||
import { registerConsoleForLogging } from "./utils/register-console-for-logging";
|
import { registerConsoleForLogging } from "./utils/register-console-for-logging";
|
||||||
import { updateEditorStatusDisplay } from "./views/editor-sync-line/editor-sync-line";
|
import { updateEditorStatusDisplay } from "./views/editor-sync-line/editor-sync-line";
|
||||||
import { remoteCursorsTheme } from "./views/remote-cursors/remote-cursor-theme";
|
import { remoteCursorsTheme } from "./views/cursors/remote-cursor-theme";
|
||||||
import { remoteCursorsPlugin } from "./views/remote-cursors/remote-cursors-plugin";
|
import {
|
||||||
|
remoteCursorsPlugin,
|
||||||
|
setCursors
|
||||||
|
} from "./views/cursors/remote-cursors-plugin";
|
||||||
|
import { getCursorsFromEditor } from "./views/cursors/get-cursors-from-editor";
|
||||||
|
import { LocalCursorUpdateListener } from "./views/cursors/local-cursor-update-listener";
|
||||||
|
|
||||||
const MIN_WAIT_BETWEEN_UPDATES_IN_MS = 250;
|
const MIN_WAIT_BETWEEN_UPDATES_IN_MS = 250;
|
||||||
export default class VaultLinkPlugin extends Plugin {
|
export default class VaultLinkPlugin extends Plugin {
|
||||||
private readonly disposables: (() => unknown)[] = [];
|
private readonly disposables: (() => unknown)[] = [];
|
||||||
|
|
||||||
private settingsTab: SyncSettingsTab | undefined;
|
private settingsTab: SyncSettingsTab | undefined;
|
||||||
private client!: SyncClient;
|
private client!: SyncClient;
|
||||||
private readonly rateLimitedUpdatesPerFile = new Map<
|
private readonly rateLimitedUpdatesPerFile = new Map<
|
||||||
|
|
@ -73,6 +82,17 @@ export default class VaultLinkPlugin extends Plugin {
|
||||||
);
|
);
|
||||||
|
|
||||||
this.registerEditorExtension([remoteCursorsTheme, remoteCursorsPlugin]);
|
this.registerEditorExtension([remoteCursorsTheme, remoteCursorsPlugin]);
|
||||||
|
|
||||||
|
this.client.addRemoteCursorsUpdateListener((cursors) => {
|
||||||
|
setCursors(cursors, this.app);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cursorListener = new LocalCursorUpdateListener(
|
||||||
|
this.client,
|
||||||
|
this.app.workspace
|
||||||
|
);
|
||||||
|
this.disposables.push(() => cursorListener.dispose());
|
||||||
|
|
||||||
this.app.workspace.updateOptions();
|
this.app.workspace.updateOptions();
|
||||||
|
|
||||||
this.addRibbonIcon(
|
this.addRibbonIcon(
|
||||||
|
|
@ -175,9 +195,7 @@ export default class VaultLinkPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
].forEach((event) => {
|
].forEach((event) => this.registerEvent(event));
|
||||||
this.registerEvent(event);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async rateLimitedUpdate(path: string): Promise<void> {
|
private async rateLimitedUpdate(path: string): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Editor } from "obsidian";
|
import type { Editor } from "obsidian";
|
||||||
import { lineAndColumnToPosition } from "./line-and-column-to-position";
|
import { lineAndColumnToPosition } from "../../utils/line-and-column-to-position";
|
||||||
|
|
||||||
export interface Cursor {
|
export interface Cursor {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
import {
|
||||||
|
EventRef,
|
||||||
|
Workspace,
|
||||||
|
Editor,
|
||||||
|
MarkdownView,
|
||||||
|
MarkdownFileInfo
|
||||||
|
} from "obsidian";
|
||||||
|
import { SyncClient } from "sync-client";
|
||||||
|
import { Cursor, getCursorsFromEditor } from "./get-cursors-from-editor";
|
||||||
|
|
||||||
|
export class LocalCursorUpdateListener {
|
||||||
|
private static readonly UPDATE_INTERVAL_MS = 50;
|
||||||
|
private readonly eventHandle: NodeJS.Timeout;
|
||||||
|
private lastCursorState: Record<string, Cursor[]> = {};
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly client: SyncClient,
|
||||||
|
private readonly workspace: Workspace
|
||||||
|
) {
|
||||||
|
this.eventHandle = setInterval(
|
||||||
|
() => this.updateAllCursors(),
|
||||||
|
LocalCursorUpdateListener.UPDATE_INTERVAL_MS
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateAllCursors(): void {
|
||||||
|
const currentCursors = this.getAllCursors();
|
||||||
|
if (
|
||||||
|
JSON.stringify(this.lastCursorState) ===
|
||||||
|
JSON.stringify(currentCursors)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.lastCursorState = currentCursors;
|
||||||
|
this.client.updateLocalCursors(currentCursors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAllCursors(): Record<string, Cursor[]> {
|
||||||
|
const cursors: Record<string, Cursor[]> = {};
|
||||||
|
this.workspace
|
||||||
|
.getLeavesOfType("markdown")
|
||||||
|
.map((leaf) => leaf.view)
|
||||||
|
.filter((view) => view instanceof MarkdownView)
|
||||||
|
.forEach((view) => {
|
||||||
|
const { file } = view;
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cursors[file.path] = getCursorsFromEditor(view.editor);
|
||||||
|
});
|
||||||
|
return cursors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
clearInterval(this.eventHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue