diff --git a/custom-editor-sample/.vscode/launch.json b/custom-editor-sample/.vscode/launch.json new file mode 100644 index 00000000..461f3da0 --- /dev/null +++ b/custom-editor-sample/.vscode/launch.json @@ -0,0 +1,18 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "npm: watch" + } + ] +} diff --git a/custom-editor-sample/.vscode/settings.json b/custom-editor-sample/.vscode/settings.json new file mode 100644 index 00000000..8d047dad --- /dev/null +++ b/custom-editor-sample/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.insertSpaces": false +} \ No newline at end of file diff --git a/custom-editor-sample/.vscode/tasks.json b/custom-editor-sample/.vscode/tasks.json new file mode 100644 index 00000000..3b17e53b --- /dev/null +++ b/custom-editor-sample/.vscode/tasks.json @@ -0,0 +1,20 @@ +// See https://go.microsoft.com/fwlink/?LinkId=733558 +// for the documentation about the tasks.json format +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/custom-editor-sample/README.md b/custom-editor-sample/README.md new file mode 100644 index 00000000..012e1b3d --- /dev/null +++ b/custom-editor-sample/README.md @@ -0,0 +1,21 @@ +# Cat Customs - Custom Editor API Samples + +Demonstrates using VS Code's [custom editor API](https://code.visualstudio.com/api/extension-guides/custom-editors): + +- Cat Scratch — A text based custom editor for `.cscratch` files (which are just json files) + +## VS Code API + +### `vscode` module + +- [`window.registerCustomEditorProvider`](https://code.visualstudio.com/api/references/vscode-api#window.registerCustomEditorProvider) +- [`CustomTextEditor`](https://code.visualstudio.com/api/references/vscode-api#CustomTextEditor) + +## Running the example + +- Open this example in VS Code 1.44+ +- `npm install` +- `npm run watch` or `npm run compile` +- `F5` to start debugging + +Open the example files from the `exampleFiles` directory. \ No newline at end of file diff --git a/custom-editor-sample/exampleFiles/example.cscratch b/custom-editor-sample/exampleFiles/example.cscratch new file mode 100644 index 00000000..ac985330 --- /dev/null +++ b/custom-editor-sample/exampleFiles/example.cscratch @@ -0,0 +1,14 @@ +{ + "scratches": [ + { + "id": "8lYOoWqz2rHtPuhvnZ43eMx1mG6WnFrm", + "text": "😸", + "created": 1584577931699 + }, + { + "id": "aZ57bJUEaXZ5wuBAX6NfGuj85Y6iw84N", + "text": "😻", + "created": 1584577933329 + }, + ] +} \ No newline at end of file diff --git a/custom-editor-sample/media/catScratch.css b/custom-editor-sample/media/catScratch.css new file mode 100644 index 00000000..f68f733c --- /dev/null +++ b/custom-editor-sample/media/catScratch.css @@ -0,0 +1,69 @@ +body { + justify-content: center; + align-items: center; + margin: 1em 2em; + background-image: url(./sand.jpg); + background-repeat: repeat; +} + +body.vscode-dark { + background-image: url(./sand-dark.jpg); +} + +.notes { + display: grid; + grid-template-columns: repeat(auto-fill, 100px); + grid-template-rows: repeat(auto-fill, 100px); + grid-gap: 2em; + justify-content: center; +} + +.note { + display: flex; + flex-direction: column; + border-radius: 5px; + background-color: var(--vscode-editor-background); + text-align: center; + padding: 0.6em; + position: relative; + overflow: hidden; +} + +.note .text { + flex: 1; + font-size: 3em; + display: flex; + justify-content: center; + align-items: center; +} + +.note .created { + font-style: italic; + font-size: 0.75em; +} + +.add-button { + height: 100px; + display: flex; + justify-content: center; + align-items: center; +} + +.add-button button { + background-color: var(--vscode-button-foreground); +} + +.delete-button { + position: absolute; + top: 0; + right: 0; + display: none; +} + +.delete-button:before { + content: 'delete'; +} + +.note:hover .delete-button { + display: block; +} \ No newline at end of file diff --git a/custom-editor-sample/media/catScratch.js b/custom-editor-sample/media/catScratch.js new file mode 100644 index 00000000..3016c85c --- /dev/null +++ b/custom-editor-sample/media/catScratch.js @@ -0,0 +1,97 @@ +// @ts-check + +// Script run within the webview itself. +(function () { + + // Get a reference to the VS Code webview api. + // We use this API to post messages back to our extension. + + // @ts-ignore + const vscode = acquireVsCodeApi(); + + + const notesContainer = /** @type {HTMLElement} */ (document.querySelector('.notes')); + + const addButtonContainer = document.querySelector('.add-button'); + addButtonContainer.querySelector('button').addEventListener('click', () => { + vscode.postMessage({ + type: 'add' + }); + }) + + const errorContainer = document.createElement('div'); + document.body.appendChild(errorContainer); + errorContainer.className = 'error' + errorContainer.style.display = 'none' + + /** + * Render the document in the webview. + */ + function updateContent(/** @type {string} */ text) { + let json; + try { + json = JSON.parse(text); + } catch { + notesContainer.style.display = 'none'; + errorContainer.innerText = 'Error: Document is not valid json'; + errorContainer.style.display = ''; + return; + } + notesContainer.style.display = ''; + errorContainer.style.display = 'none'; + + // Render the scratches + notesContainer.innerHTML = ''; + for (const note of json.scratches || []) { + const element = document.createElement('div'); + element.className = 'note'; + notesContainer.appendChild(element); + + const text = document.createElement('div'); + text.className = 'text'; + const textContent = document.createElement('span'); + textContent.innerText = note.text; + text.appendChild(textContent); + element.appendChild(text); + + const created = document.createElement('div'); + created.className = 'created'; + created.innerText = new Date(note.created).toUTCString(); + element.appendChild(created); + + const deleteButton = document.createElement('button'); + deleteButton.className = 'delete-button'; + deleteButton.addEventListener('click', () => { + vscode.postMessage({ type: 'delete', id: note.id, }); + }); + element.appendChild(deleteButton); + } + + notesContainer.appendChild(addButtonContainer); + } + + // Handle messages sent from the extension to the webview + window.addEventListener('message', event => { + const message = event.data; // The json data that the extension sent + switch (message.type) { + case 'update': + const text = message.text; + + // Update our webview's content + updateContent(text); + + // Then persist state information. + // This state is returned in the call to `vscode.getState` below when a webview is reloaded. + vscode.setState({ text }); + + return; + } + }); + + // Webviews are normally torn down when not visible and re-created when they become visible again. + // State lets us save information across these re-loads + const state = vscode.getState(); + if (state) { + updateContent(state.text); + } +}()); \ No newline at end of file diff --git a/custom-editor-sample/media/sand-dark.jpg b/custom-editor-sample/media/sand-dark.jpg new file mode 100644 index 00000000..6d524a1f Binary files /dev/null and b/custom-editor-sample/media/sand-dark.jpg differ diff --git a/custom-editor-sample/media/sand.jpg b/custom-editor-sample/media/sand.jpg new file mode 100644 index 00000000..6b86e9ec Binary files /dev/null and b/custom-editor-sample/media/sand.jpg differ diff --git a/custom-editor-sample/package-lock.json b/custom-editor-sample/package-lock.json new file mode 100644 index 00000000..5399d4ca --- /dev/null +++ b/custom-editor-sample/package-lock.json @@ -0,0 +1,323 @@ +{ + "name": "cat-edit", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@types/node": { + "version": "10.12.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.26.tgz", + "integrity": "sha512-nMRqS+mL1TOnIJrL6LKJcNZPB8V3eTfRo9FQA2b5gDvrHurC8XbSA86KNe0dShlEL7ReWJv/OU9NL7Z0dnqWTg==", + "dev": true + }, + "@types/vscode": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.43.0.tgz", + "integrity": "sha512-kIaR9qzd80rJOxePKpCB/mdy00mz8Apt2QA5Y6rdrKFn13QNFNeP3Hzmsf37Bwh/3cS7QjtAeGSK7wSqAU0sYQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "resolve": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", + "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", + "dev": true + }, + "tslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.16.0.tgz", + "integrity": "sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.13.0", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + } + } +} diff --git a/custom-editor-sample/package.json b/custom-editor-sample/package.json new file mode 100644 index 00000000..6d356395 --- /dev/null +++ b/custom-editor-sample/package.json @@ -0,0 +1,48 @@ +{ + "name": "cat-customs", + "displayName": "Cat Customs", + "description": "Custom Editor API Samples", + "version": "0.0.1", + "enableProposedApi": true, + "publisher": "vscode-samples", + "engines": { + "vscode": "^1.44.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onCustomEditor:catCustoms.catScratch" + ], + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode-extension-samples.git" + }, + "main": "./out/extension.js", + "contributes": { + "customEditors": [ + { + "viewType": "catCustoms.catScratch", + "displayName": "Cat Scratch", + "selector": [ + { + "filenamePattern": "*.cscratch" + } + ] + } + ] + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "lint": "tslint -p ./", + "watch": "tsc -w -p ./" + }, + "dependencies": {}, + "devDependencies": { + "@types/node": "^10.5.2", + "tslint": "^5.16.0", + "typescript": "^3.8.3", + "@types/vscode": "^1.43.0" + } +} \ No newline at end of file diff --git a/custom-editor-sample/src/catScratchEditor.ts b/custom-editor-sample/src/catScratchEditor.ts new file mode 100644 index 00000000..66d700fd --- /dev/null +++ b/custom-editor-sample/src/catScratchEditor.ts @@ -0,0 +1,199 @@ +import * as path from 'path'; +import * as vscode from 'vscode'; +import { getNonce } from './util'; + +/** + * Provider for cat scratch editors. + * + * Cat scratch editors are used for `.cscratch` files, which are just json files. + * To get started, run this extension and open an empty `.cscratch` file in VS Code. + * + * This provider demonstrates: + * + * - Setting up the initial webview for a custom editor. + * - Loading scripts and styles in a custom editor. + * - Synchronizing changes between a text document and a custom editor. + */ +export class CatScratchEditorProvider implements vscode.CustomTextEditorProvider { + + public static register(context: vscode.ExtensionContext): vscode.Disposable { + const provider = new CatScratchEditorProvider(context); + const providerRegistration = vscode.window.registerCustomEditorProvider(CatScratchEditorProvider.viewType, provider); + return providerRegistration; + } + + private static readonly viewType = 'catCustoms.catScratch'; + + private static readonly scratchCharacters = ['😸', '😹', '😺', '😻', '😼', '😽', '😾', '🙀', '😿', '🐱']; + + constructor( + private readonly context: vscode.ExtensionContext + ) { } + + /** + * Called when our custom editor is opened. + * + * + */ + public async resolveCustomTextEditor( + document: vscode.TextDocument, + webviewPanel: vscode.WebviewPanel, + _token: vscode.CancellationToken + ): Promise { + // Setup initial content for the webview + webviewPanel.webview.options = { + enableScripts: true, + }; + webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview); + + function updateWebview() { + webviewPanel.webview.postMessage({ + type: 'update', + text: document.getText(), + }); + } + + // Hook up event handlers so that we can synchronize the webview with the text document. + // + // The text document acts as our model, so we have to sync change in the document to our + // editor and sync changes in the editor back to the document. + // + // Remember that a single text document can also be shared between multiple custom + // editors (this happens for example when you split a custom editor) + + const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(e => { + if (e.document.uri.toString() === document.uri.toString()) { + updateWebview(); + } + }); + + // Make sure we get rid of the listener when our editor is closed. + webviewPanel.onDidDispose(() => { + changeDocumentSubscription.dispose(); + }); + + // Receive message from the webview. + webviewPanel.webview.onDidReceiveMessage(e => { + switch (e.type) { + case 'add': + this.addNewScratch(document); + return; + + case 'delete': + this.deleteScratch(document, e.id); + return; + } + }); + + updateWebview(); + } + + /** + * Get the static html used for the editor webviews. + */ + private getHtmlForWebview(webview: vscode.Webview): string { + // Local path to script and css for the webview + const scriptUri = webview.asWebviewUri(vscode.Uri.file( + path.join(this.context.extensionPath, 'media', 'catScratch.js') + )); + const styleUri = webview.asWebviewUri(vscode.Uri.file( + path.join(this.context.extensionPath, 'media', 'catScratch.css') + )); + + // Use a nonce to whitelist which scripts can be run + const nonce = getNonce(); + + return /* html */` + + + + + + + + + + + + + Cat Scratch + + +
+
+ +
+
+ + + + `; + } + + /** + * Add a new scratch to the current document. + */ + private addNewScratch(document: vscode.TextDocument) { + const json = this.getDocumentAsJson(document); + const character = CatScratchEditorProvider.scratchCharacters[Math.floor(Math.random() * CatScratchEditorProvider.scratchCharacters.length)]; + json.scratches = [ + ...(Array.isArray(json.scratches) ? json.scratches : []), + { + id: getNonce(), + text: character, + created: Date.now(), + } + ]; + + return this.updateTextDocument(document, json); + } + + /** + * Delete an existing scratch from a document. + */ + private deleteScratch(document: vscode.TextDocument, id: string) { + const json = this.getDocumentAsJson(document); + if (!Array.isArray(json.scratches)) { + return; + } + + json.scratches = json.scratches.filter((note: any) => note.id !== id); + + return this.updateTextDocument(document, json); + } + + /** + * Try to get a current document as json text. + */ + private getDocumentAsJson(document: vscode.TextDocument): any { + const text = document.getText(); + if (text.trim().length === 0) { + return {}; + } + + try { + return JSON.parse(text); + } catch { + throw new Error('Could not get document as json. Content is not valid json'); + } + } + + /** + * Write out the json to a given document. + */ + private updateTextDocument(document: vscode.TextDocument, json: any) { + const edit = new vscode.WorkspaceEdit(); + + // Just replace the entire document every time for this example extension. + // A more complete extension should compute minimal edits instead. + edit.replace( + document.uri, + new vscode.Range(0, 0, document.lineCount, 0), + JSON.stringify(json, null, 2)); + + return vscode.workspace.applyEdit(edit); + } +} diff --git a/custom-editor-sample/src/extension.ts b/custom-editor-sample/src/extension.ts new file mode 100644 index 00000000..d4f5e158 --- /dev/null +++ b/custom-editor-sample/src/extension.ts @@ -0,0 +1,7 @@ +import * as vscode from 'vscode'; +import { CatScratchEditorProvider } from './catScratchEditor'; + +export function activate(context: vscode.ExtensionContext) { + // Register our custom editor provider + context.subscriptions.push(CatScratchEditorProvider.register(context)); +} diff --git a/custom-editor-sample/src/util.ts b/custom-editor-sample/src/util.ts new file mode 100644 index 00000000..1e54bdff --- /dev/null +++ b/custom-editor-sample/src/util.ts @@ -0,0 +1,8 @@ +export function getNonce() { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/custom-editor-sample/tsconfig.json b/custom-editor-sample/tsconfig.json new file mode 100644 index 00000000..bf48573f --- /dev/null +++ b/custom-editor-sample/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "sourceMap": true, + "strict": true, + "rootDir": "src", + }, + "exclude": ["node_modules", ".vscode-test"], + +} diff --git a/custom-editor-sample/tslint.json b/custom-editor-sample/tslint.json new file mode 100644 index 00000000..0ab0ca6e --- /dev/null +++ b/custom-editor-sample/tslint.json @@ -0,0 +1,6 @@ +{ + "rules": { + "indent": [true, "tabs"], + "semicolon": [true, "always"] + } +} \ No newline at end of file