Skip to content

Commit fe36ace

Browse files
authored
Merge pull request #581 from mre/backup-system
Backup system
2 parents c5a78fb + 0f696a0 commit fe36ace

17 files changed

+572
-74
lines changed

README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ A Visual Studio Code extension for [cht.sh](https://cht.sh/).
2727
- ${index} - the index of the snippet (e.g. 2 for the third answer)
2828
- `insertWithDoubleClick`: insert snippet with double click.
2929
- `showCopySuccessNotification`: Whether to show a notification after the snippet is copied to the clipboard.
30+
- `saveBackups`: Whether to create backups of the snippets.
3031

3132
## Installation
3233

@@ -132,6 +133,41 @@ Saved snippets are displayed in IntelliSense
132133

133134
![Preview](https://raw.githubusercontent.com/mre/vscode-snippet/master/contrib/snippets-storage/search.gif)
134135

136+
## Restoring snippets from backups
137+
138+
### Restoring with the built-in backup mechanism
139+
140+
vscode-snippet creates backups of your snippets when you delete, rename, move or save snippets. These backups are stored **locally** on your computer.
141+
142+
To restore a backup:
143+
144+
1. Open the Snippets section
145+
2. Click on the ![History icon](https://raw.githubusercontent.com/mre/vscode-snippet/master/contrib/snippets-storage/history.png) icon (alternatively, you can run the "Restore backups" command)
146+
3. Select one of the backups from the list
147+
148+
![Demo of restoring backups](https://raw.githubusercontent.com/mre/vscode-snippet/master/contrib/snippets-storage/restore-backups.gif)
149+
150+
### Restoring with the VSCode settings sync
151+
152+
If you have [VSCode settings sync](https://code.visualstudio.com/docs/editor/settings-sync) enabled, you can restore snippets by using VSCode's built-in backup mechanisms: [https://code.visualstudio.com/docs/editor/settings-sync#\_restoring-data](https://code.visualstudio.com/docs/editor/settings-sync#_restoring-data)
153+
154+
## Exporting snippets
155+
156+
VSCode stores snippets in the `state.vscdb` file in a `JSON` format.
157+
158+
To export the snippets:
159+
160+
1. Find the `state.vscdb` file
161+
- On Ubuntu Linux: `~/.config/Code/User/globalStorage/state.vscdb`
162+
- On Windows: `AppData\Roaming\Code\User\globalStorage\state.vscdb`
163+
- On macOS: `~/Library/Application Support/Code/User/globalStorage/state.vscdb`
164+
2. Inspect the content of this file using some tool that can open SQLite files, for example: [https://inloop.github.io/sqlite-viewer](https://inloop.github.io/sqlite-viewer)
165+
1. On this website, upload the `state.vscdb` file and run the following command:
166+
```sql
167+
SELECT * FROM 'ItemTable' WHERE key like 'vscode-snippet.snippet'
168+
```
169+
![SQLite Viewer](https://raw.githubusercontent.com/mre/vscode-snippet/master/contrib/snippets-storage/vscdb.png) 2. Then click "Execute". You should get a single row with the key `vscode-snippet.snippet` and a `JSON` value. This `JSON` contains all of your snippets.
170+
135171
## Contributing
136172

137173
See [CONTRIBUTING.md](./CONTRIBUTING.md)

assets/icons/history-dark.svg

Lines changed: 3 additions & 0 deletions
Loading

assets/icons/history-light.svg

Lines changed: 3 additions & 0 deletions
Loading

contrib/snippets-storage/history.png

437 Bytes
Loading
405 KB
Loading

contrib/snippets-storage/vscdb.png

52.6 KB
Loading

package-lock.json

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

package.json

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "snippet",
33
"displayName": "Snippet",
44
"description": "Insert a snippet from cht.sh for Python, JavaScript, Ruby, C#, Go, Rust (and any other language)",
5-
"version": "1.1.5",
5+
"version": "1.1.6",
66
"publisher": "vscode-snippet",
77
"engines": {
88
"vscode": "^1.74.0"
@@ -123,6 +123,15 @@
123123
"light": "assets/icons/add-light.svg",
124124
"dark": "assets/icons/add-dark.svg"
125125
}
126+
},
127+
{
128+
"title": "Restore backups",
129+
"command": "snippet.restoreBackups",
130+
"category": "Snippet",
131+
"icon": {
132+
"light": "assets/icons/history-light.svg",
133+
"dark": "assets/icons/history-dark.svg"
134+
}
126135
}
127136
],
128137
"configuration": {
@@ -162,6 +171,11 @@
162171
"type": "boolean",
163172
"default": true,
164173
"description": "Whether to show a notification after the snippet is copied to the clipboard."
174+
},
175+
"snippet.saveBackups": {
176+
"type": "boolean",
177+
"default": true,
178+
"description": "Whether to create backups of the snippets."
165179
}
166180
}
167181
},
@@ -171,6 +185,11 @@
171185
"command": "snippet.createFolder",
172186
"when": "view == snippetsView",
173187
"group": "navigation"
188+
},
189+
{
190+
"command": "snippet.restoreBackups",
191+
"when": "view == snippetsView",
192+
"group": "navigation"
174193
}
175194
],
176195
"view/item/context": [

src/backupManager.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { randomUUID } from "crypto";
2+
import * as vscode from "vscode";
3+
import { getConfig } from "./config";
4+
import { formatUnixTime } from "./date";
5+
import SnippetsStorage, {
6+
StorageOperation,
7+
TreeElement,
8+
} from "./snippetsStorage";
9+
10+
export interface Backup {
11+
id: string;
12+
dateUnix: number;
13+
elements: TreeElement[];
14+
beforeOperation?: string;
15+
}
16+
17+
export interface BackupItem extends vscode.QuickPickItem {
18+
item: Backup;
19+
}
20+
21+
const STORAGE_KEY = "snippet.snippetBackupsStorageKey";
22+
const MAX_BACKUPS = 10;
23+
24+
export class BackupManager {
25+
private backups: Backup[] = [];
26+
private elementsBeforeRestore: TreeElement[] | null = null;
27+
28+
constructor(
29+
private readonly context: vscode.ExtensionContext,
30+
private readonly snippets: SnippetsStorage
31+
) {
32+
this.load();
33+
snippets.onBeforeSave = (elements, operation) =>
34+
this.makeBackup(elements, operation);
35+
}
36+
37+
getBackupItems(): BackupItem[] {
38+
const items = this.backups.map((backup) => {
39+
const time = `${formatUnixTime(backup.dateUnix)}`;
40+
const detail = backup.beforeOperation
41+
? `before "${backup.beforeOperation}"`
42+
: undefined;
43+
const description = `${this.snippets.getSnippetCount(
44+
backup.elements
45+
)} snippet${
46+
this.snippets.getSnippetCount(backup.elements) === 1 ? "" : "s"
47+
}`;
48+
49+
return {
50+
label: time,
51+
item: backup,
52+
description,
53+
detail,
54+
};
55+
});
56+
57+
items.sort((a, b) => b.item.dateUnix - a.item.dateUnix);
58+
59+
return items;
60+
}
61+
62+
async restoreBackup(id: string) {
63+
const backup = this.backups.find((backup) => backup.id === id);
64+
65+
if (!backup) {
66+
console.error(`Backup with id ${id} not found.`);
67+
return;
68+
}
69+
70+
this.elementsBeforeRestore = this.snippets.getElements();
71+
await this.snippets.replaceElements(backup.elements);
72+
}
73+
74+
async undoLastRestore() {
75+
if (this.elementsBeforeRestore === null) {
76+
return;
77+
}
78+
79+
await this.snippets.replaceElements(this.elementsBeforeRestore);
80+
this.elementsBeforeRestore = null;
81+
}
82+
83+
private load(): void {
84+
this.backups = JSON.parse(
85+
this.context.globalState.get(STORAGE_KEY) || "[]"
86+
) as Backup[];
87+
}
88+
89+
private async makeBackup(
90+
elements: TreeElement[],
91+
operation?: StorageOperation
92+
) {
93+
if (!getConfig("saveBackups")) {
94+
return;
95+
}
96+
97+
const backup: Backup = {
98+
id: randomUUID(),
99+
dateUnix: Date.now(),
100+
elements,
101+
beforeOperation: operation,
102+
};
103+
104+
this.backups.push(backup);
105+
106+
if (this.backups.length > MAX_BACKUPS) {
107+
this.backups.shift();
108+
}
109+
110+
await this.save();
111+
}
112+
113+
private async save() {
114+
await this.context.globalState.update(
115+
STORAGE_KEY,
116+
JSON.stringify(this.backups)
117+
);
118+
}
119+
}

src/config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ export async function pickLanguage() {
1717
const languages = await vscode.languages.getLanguages();
1818
const disposables: Disposable[] = [];
1919

20-
// return await vscode.window.showQuickPick(languages);
21-
2220
try {
2321
return await new Promise<string | undefined>((resolve) => {
2422
const input = vscode.window.createQuickPick<LanguageItem>();

src/date.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export function formatUnixTime(ms: number): string {
2+
const date = new Date(ms);
3+
return `${date.toDateString()}, ${date.toLocaleTimeString([], {
4+
hour: "2-digit",
5+
minute: "2-digit",
6+
})}`;
7+
}

src/endpoints.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import * as vscode from "vscode";
2+
import { BackupManager } from "./backupManager";
23
import * as clipboard from "./clipboard";
3-
import { pickLanguage, getLanguage, getConfig } from "./config";
4-
import { query } from "./query";
4+
import { getConfig, getLanguage, pickLanguage } from "./config";
5+
import { formatUnixTime } from "./date";
6+
import languages from "./languages";
57
import { encodeRequest } from "./provider";
8+
import { query } from "./query";
69
import snippet from "./snippet";
7-
import { SnippetsTreeProvider, SnippetsTreeItem } from "./snippetsTreeProvider";
810
import SnippetsStorage from "./snippetsStorage";
9-
import languages from "./languages";
11+
import { SnippetsTreeItem, SnippetsTreeProvider } from "./snippetsTreeProvider";
1012

1113
export interface Request {
1214
language: string;
@@ -383,3 +385,30 @@ export function createFolder(treeProvider: SnippetsTreeProvider) {
383385
await treeProvider.storage.createFolder(folderName, item?.id);
384386
};
385387
}
388+
389+
export function showBackups(backupManager: BackupManager) {
390+
return async () => {
391+
const backups = backupManager.getBackupItems();
392+
const selectedBackup = await vscode.window.showQuickPick(backups, {
393+
placeHolder:
394+
"Select a backup to restore. You will be able to undo this operation.",
395+
title: "Select a backup",
396+
});
397+
398+
if (!selectedBackup) {
399+
return;
400+
}
401+
402+
await backupManager.restoreBackup(selectedBackup.item.id);
403+
await vscode.commands.executeCommand("snippetsView.focus");
404+
const answer = await vscode.window.showInformationMessage(
405+
`Restored backup from ${formatUnixTime(selectedBackup.item.dateUnix)}`,
406+
"Ok",
407+
"Undo"
408+
);
409+
410+
if (answer === "Undo") {
411+
await backupManager.undoLastRestore();
412+
}
413+
};
414+
}

0 commit comments

Comments
 (0)