-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.ts
190 lines (163 loc) · 5.32 KB
/
main.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import {EditorPosition, MarkdownView, Plugin, WorkspaceLeaf,} from 'obsidian';
const isDev = process.env.NODE_ENV === 'development';
interface PluginSettings {
tabs: Record<string, TabState>;
}
const DEFAULT_SETTINGS: PluginSettings = {
tabs: {}
}
interface TabState {
id: string;
name: string;
index: number;
cursor: EditorPosition;
scroll: {
top: number;
left: number;
}
}
// noinspection JSUnusedGlobalSymbols
export default class RememberViewStatePlugin extends Plugin {
settings: PluginSettings;
initialized = false;
loadedTabs = new Map<string, boolean>();
async onload() {
this.registerEvent(this.app.workspace.on('active-leaf-change', this.onLeafChange));
this.app.workspace.onLayoutReady(() => {
this.restoreActiveLeaves();
});
this.removeOrphanLeaves()
}
onunload() {
}
removeOrphanLeaves = async () => {
if (!this.initialized) {
return;
}
// Get the IDs of currently open markdown leaves
const openLeafIds = new Set(
// @ts-ignore this is not visible, yet the best way to get an id
this.app.workspace.getLeavesOfType('markdown').map(leaf => leaf.id)
);
// Check which tabs in settings are no longer open and remove them
Object.keys(this.settings.tabs).forEach(tabId => {
if (!openLeafIds.has(tabId)) {
delete this.settings.tabs[tabId]; // Remove the tab from settings
if (isDev) {
console.log(`Removed closed tab ${tabId} from settings.`);
}
}
});
await this.saveSettings();
}
async saveSettings() {
await this.saveData(this.settings);
console.log('Settings saved');
}
private async saveTabsStates() {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view) {
console.log('⏎ not saving because no active view found');
return
}
// Save all and avoide mixing things up when tabs are moved around, closed, etc.
this.app.workspace.getLeavesOfType('markdown')
.filter(v => (v.view as MarkdownView).editor != null)
.forEach((leaf, index) => {
const view = leaf.view as MarkdownView;
const cursor = view.editor.getCursor()
const name = view.getDisplayText();
// @ts-ignore
const id = leaf.id;
const scrollInfo = view.editor.getScrollInfo()
if (cursor.line === 0) {
console.log(`Not saving ${name} with 0 cursor`);
return;
}
this.settings.tabs[id] = {id, name, cursor, scroll: scrollInfo, index}
})
console.log('💾 Saving tabs', this.settings.tabs)
await this.saveSettings();
}
async restoreActiveLeaves() {
if (this.initialized) {
return
}
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
// initialize tabs with all open tabs
const leaves = this.app.workspace.getLeavesOfType('markdown');
if (isDev) {
console.log("Loading tabs", this.settings.tabs)
console.log('views ', leaves.length)
}
// set initialized to true here. If something fails we bail out
this.initialized = true;
// set all tabs at the saved offset
leaves
// .map(leaf => {
// console.log(leaf)
// return leaf;
// })
.filter(v => (v.view as MarkdownView).editor != null)
.forEach((leaf, index) => {
this.restoreLeaf(leaf);
})
}
private async restoreLeaf(leaf: WorkspaceLeaf) {
await leaf.loadIfDeferred();
const view = leaf.view as MarkdownView;
// @ts-ignore this is not visible, yet the best way to get an id that's
let leafId = leaf.id;
this.loadedTabs.set(leafId, true);
const cursor = this.settings.tabs[leafId]?.cursor ?? {line: 0, ch: 0};
let viewCursor = view.editor.getCursor();
if (viewCursor.line !== 0 || cursor.line === viewCursor.line) {
console.log(`⏎ Ignoring ${leafId} ${view.getDisplayText()} with unchanged or 0 cursor (${viewCursor.line}) `);
return;
}
view.editor.setCursor(cursor);
// attempting to set the scroll position
// NOTE this is not working as expected
// The problem is getScrollInfo() returns a different object than the one we saved.
// Moreover, it returns a value in pixels and this sets one in lines.
// Moreover, none of the APIs are documented.
// This centers the scroll to cursor position
view.editor.scrollIntoView({
from: {line: cursor.line, ch: cursor.ch},
to: {line: cursor.line, ch: cursor.ch}
}, true)
}
onLeafChange = async (leaf: WorkspaceLeaf) => {
console.log('⚡️leaf change', leaf);
if (!this.initialized) {
console.log('⏎ leaf change - early EXIT because not initialized', leaf);
return
}
// @ts-ignore
let leafId = leaf.id;
let isLoaded = this.loadedTabs.has(leafId);
if (isLoaded) {
console.log(`DISABLED RETURN ⏎ leafId ${leafId} already loaded returning`);
// 👇👇👇👇👇👇👇👇👇👇👇👇👇
// doulbe trigger event on first editor
// return;
}
let tabStates = Object.values(this.settings.tabs).filter(v => v.id === leafId);
let hasState = tabStates.length > 0;
if (!hasState) {
// 🚨🚨🚨🚨🚨🚨🚨
// I suspect we ended up breaking this after we started using it for both read and write
// triggering an early exit
console.log(`leaf ${leafId} no state found to restore`);
} else {
// delete - think it's handled in restoreLEaf
// if (tabStates[0].cursor.line === 0) {
// console.log(`leaf ${leafId} ignoring empty state`);
// }
this.loadedTabs.set(leafId, true)
this.restoreLeaf(leaf)
console.log(`leaf ${leafId} restored `);
}
await this.saveTabsStates();
}
}