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..36958e90 --- /dev/null +++ b/custom-editor-sample/README.md @@ -0,0 +1,23 @@ +# Cat Customs - Custom Editor API Samples + +Demonstrates VS Code's [custom editor API](TODO) using two custom editors: + +- Cat Scratch — A text based custom editor for `.cscratch` files (which are just json files) +- Paw Draw - A binary custom editor for `.pawdraw` files (which are just jpeg files with a different file extension) + +## 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) +- [`CustomEditor`](https://code.visualstudio.com/api/references/vscode-api#CustomEditor) + +## 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/exampleFiles/example.pawDraw b/custom-editor-sample/exampleFiles/example.pawDraw new file mode 100644 index 00000000..941ea9c5 Binary files /dev/null and b/custom-editor-sample/exampleFiles/example.pawDraw differ 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/paw-color.svg b/custom-editor-sample/media/paw-color.svg new file mode 100644 index 00000000..b358cca2 --- /dev/null +++ b/custom-editor-sample/media/paw-color.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/custom-editor-sample/media/paw-outline.svg b/custom-editor-sample/media/paw-outline.svg new file mode 100644 index 00000000..0227ff54 --- /dev/null +++ b/custom-editor-sample/media/paw-outline.svg @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/custom-editor-sample/media/pawDraw.css b/custom-editor-sample/media/pawDraw.css new file mode 100644 index 00000000..faedcd24 --- /dev/null +++ b/custom-editor-sample/media/pawDraw.css @@ -0,0 +1,79 @@ +html, body { + height: 100%; +} + +.drawing-canvas { + width: 100%; + height: 100%; + flex: 1; + display: flex; + justify-content: center; + align-items: center; + background-repeat: repeat; + flex-direction: column; +} + +.drawing-controls { + position: fixed; + bottom: 0; + left: 0; + width: 100%; + display: flex; + justify-content: center; +} + +.drawing-controls button { + position: relative; + width: 100px; + height: 100px; + background: none; + border: none; + transform: translateY(30%); + transition: transform 0.1s linear; + outline: none; +} + +.drawing-controls button.active, +.drawing-controls button:hover { + transform: translateY(10%); +} + +.drawing-controls button:before, +.drawing-controls button:after { + display: block; + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.drawing-controls button:before { + -webkit-mask: url("./paw-color.svg") no-repeat 50% 50%; +} + +.drawing-controls button:after { + background-color: #111; + -webkit-mask: url("./paw-outline.svg") no-repeat 50% 50%; +} + +.drawing-controls button.black:before { + background-color: #333; +} + +.drawing-controls button.white:before { + background-color: white; +} + +.drawing-controls button.red:before { + background-color: red; +} + +.drawing-controls button.green:before { + background-color: green; +} + +.drawing-controls button.blue:before { + background-color: blue; +} \ No newline at end of file diff --git a/custom-editor-sample/media/pawDraw.js b/custom-editor-sample/media/pawDraw.js new file mode 100644 index 00000000..57ca7f9b --- /dev/null +++ b/custom-editor-sample/media/pawDraw.js @@ -0,0 +1,222 @@ +// @ts-check + +// This script is run within the webview itself +(function () { + // @ts-ignore + const vscode = acquireVsCodeApi(); + + /** + * A drawn line. + */ + class Stroke { + constructor(/** @type {string} */ color, /** @type {Array<[number, number]> | undefined} */ stroke) { + this.color = color; + /** @type {Array<[number, number]>} */ + this.stroke = stroke || []; + } + + addPoint(/** @type {number} */ x, /** @type {number} */ y) { + this.stroke.push([x, y]) + } + } + + /** + * @param {Uint8Array} initialContent + * @return {Promise} + */ + async function loadImageFromData(initialContent) { + const blob = new Blob([initialContent], { 'type': 'image/png' }); + const url = URL.createObjectURL(blob); + try { + const img = document.createElement('img'); + img.crossOrigin = 'anonymous'; + img.src = url; + await new Promise((resolve, reject) => { + img.onload = resolve; + img.onerror = reject; + }); + return img; + } finally { + URL.revokeObjectURL(url); + } + } + + class Editor { + constructor( /** @type {HTMLElement} */ parent) { + this.ready = false; + + this.drawingColor = 'black'; + + /** @type {Array} */ + this.strokes = []; + + /** @type {Stroke | undefined} */ + this.currentStroke = undefined; + + this._initElements(parent); + } + + addPoint(/** @type {number} */ x, /** @type {number} */ y) { + if (this.currentStroke) { + this.currentStroke.addPoint(x, y) + } + } + + beginStoke(/** @type {string} */ color) { + this.currentStroke = new Stroke(color); + this.strokes.push(this.currentStroke); + } + + endStroke() { + const previous = this.currentStroke; + this.currentStroke = undefined; + return previous; + } + + setStrokes(/** @type {Array} */ strokes) { + this.strokes = strokes; + this._redraw(); + } + + _initElements(/** @type {HTMLElement} */ parent) { + const colorButtons = /** @type {NodeListOf} */ (document.querySelectorAll('.drawing-controls button')); + for (const colorButton of colorButtons) { + colorButton.addEventListener('click', e => { + e.stopPropagation(); + colorButtons.forEach(button => button.classList.remove('active')); + colorButton.classList.add('active'); + this.drawingColor = colorButton.dataset['color']; + }); + } + + this.wrapper = document.createElement('div'); + this.wrapper.style.position = 'relative'; + parent.append(this.wrapper); + + this.initialCanvas = document.createElement('canvas'); + this.initialCtx = this.initialCanvas.getContext('2d'); + this.wrapper.append(this.initialCanvas); + + this.drawingCanvas = document.createElement('canvas'); + this.drawingCanvas.style.position = 'absolute'; + this.drawingCanvas.style.top = '0'; + this.drawingCanvas.style.left = '0'; + this.drawingCtx = this.drawingCanvas.getContext('2d'); + this.wrapper.append(this.drawingCanvas); + + let isDrawing = false + + parent.addEventListener('mousedown', () => { + if (!this.ready) { + return; + } + + this.beginStoke(this.drawingColor); + this.drawingCtx.strokeStyle = this.drawingColor; + + isDrawing = true; + document.body.classList.add('isDrawing'); + this.drawingCtx.beginPath(); + }); + + document.body.addEventListener('mouseup', async () => { + if (!isDrawing || !this.ready) { + return; + } + + isDrawing = false; + document.body.classList.remove('isDrawing'); + this.drawingCtx.closePath(); + + const stroke = this.endStroke(); + + vscode.postMessage({ + type: 'stroke', + color: this.drawingColor, + stroke: stroke.stroke, + }); + }); + + parent.addEventListener('mousemove', e => { + if (!isDrawing || !this.ready) { + return; + } + const rect = this.wrapper.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + this.drawingCtx.lineTo(x, y); + this.drawingCtx.stroke(); + this.addPoint(x, y); + }); + } + + _redraw() { + this.drawingCtx.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height); + for (const stroke of this.strokes) { + this.drawingCtx.strokeStyle = stroke.color; + this.drawingCtx.beginPath(); + for (const [x, y] of stroke.stroke) { + this.drawingCtx.lineTo(x, y); + } + this.drawingCtx.stroke(); + this.drawingCtx.closePath(); + } + } + + setInitialImage(/** @type {HTMLImageElement} */ img) { + this.initialCanvas.width = this.drawingCanvas.width = img.naturalWidth; + this.initialCanvas.height = this.drawingCanvas.height = img.naturalHeight; + this.initialCtx.drawImage(img, 0, 0); + this.ready = true; + this._redraw(); + } + + /** @return {Promise} */ + async getImageData() { + const outCanvas = document.createElement('canvas'); + outCanvas.width = this.drawingCanvas.width; + outCanvas.height = this.drawingCanvas.height; + + const outCtx = outCanvas.getContext('2d'); + outCtx.drawImage(this.initialCanvas, 0, 0); + outCtx.drawImage(this.drawingCanvas, 0, 0); + + const blob = await new Promise(resolve => { + outCanvas.toBlob(resolve, 'image/jpeg') + }); + + return new Uint8Array(await blob.arrayBuffer()); + } + } + + const editor = new Editor(document.querySelector('.drawing-canvas')); + + // Handle messages from the extension + window.addEventListener('message', async e => { + const { type, body, requestId } = e.data; + switch (type) { + case 'init': + // Load the initial image into the canvas. + const initialContent = new Uint8Array(body.value.data); + const img = await loadImageFromData(initialContent); + editor.setInitialImage(img); + return; + + case 'update': + // Set the drawing strokes. + editor.setStrokes(body.edits.map(edit => new Stroke(edit.color, edit.stroke))) + return; + + case 'getFileData': + // Get the image data for the canvas and post it back to the extension. + editor.getImageData().then(data => { + vscode.postMessage({ type: 'response', requestId, body: data }); + }); + return; + } + }); + + // Signal to VS Code that the webview is initilized. + vscode.postMessage({ type: 'ready' }); +}()); + 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..28af3d24 --- /dev/null +++ b/custom-editor-sample/package.json @@ -0,0 +1,57 @@ +{ + "name": "cat-edit", + "description": "Cat Customs - Custom Editor API Samples", + "version": "0.0.1", + "enableProposedApi": true, + "publisher": "vscode-samples", + "engines": { + "vscode": "^1.43.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onCustomEditor:catEdit.catScratch", + "onCustomEditor:catEdit.pawDraw" + ], + "repository": { + "type": "git", + "url": "https://github.com/microsoft/vscode-extension-samples.git" + }, + "main": "./out/extension.js", + "contributes": { + "customEditors": [ + { + "viewType": "catEdit.catScratch", + "displayName": "Cat Scratch", + "selector": [ + { + "filenamePattern": "*.cscratch" + } + ] + }, + { + "viewType": "catEdit.pawDraw", + "displayName": "Paw Draw", + "selector": [ + { + "filenamePattern": "*.pawdraw" + } + ] + } + ] + }, + "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" + } +} diff --git a/custom-editor-sample/src/catScratchEditor.ts b/custom-editor-sample/src/catScratchEditor.ts new file mode 100644 index 00000000..c68a47a7 --- /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 { + return vscode.window.registerCustomEditorProvider( + CatScratchEditorProvider.viewType, + new CatScratchEditorProvider(context)); + } + + private static readonly viewType = 'catEdit.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 exisitng 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..5baac788 --- /dev/null +++ b/custom-editor-sample/src/extension.ts @@ -0,0 +1,9 @@ +import * as vscode from 'vscode'; +import { CatScratchEditorProvider } from './catScratchEditor'; +import { PawDrawEditorProvider } from './pawDrawEditor'; + +export function activate(context: vscode.ExtensionContext) { + // Register our custom editor providers + context.subscriptions.push(CatScratchEditorProvider.register(context)); + context.subscriptions.push(PawDrawEditorProvider.register(context)); +} diff --git a/custom-editor-sample/src/pawDrawEditor.ts b/custom-editor-sample/src/pawDrawEditor.ts new file mode 100644 index 00000000..10a9f6cb --- /dev/null +++ b/custom-editor-sample/src/pawDrawEditor.ts @@ -0,0 +1,305 @@ +import * as crypto from 'crypto'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { getNonce } from './util'; + +/** + * Define the type of edits used in paw draw files. + */ +interface PawDrawEdit { + readonly color: string; + readonly stroke: ReadonlyArray<[number, number]>; +} + +/** + * Define our document type. + */ +class PawDrawDocument extends vscode.CustomDocument { + constructor( + uri: vscode.Uri, + public readonly initialContent: Uint8Array, + ) { + super(PawDrawEditorProvider.viewType, uri); + } +} + +/** + * Provider for paw draw editors. + * + * Paw draw editors are used for `.pawDraw` files, which are just `.png` files with a different file extension. + * + * This provider demonstrates: + * + * - How to implement a custom editor for binary files. + * - Setting up the initial webview for a custom editor. + * - Loading scripts and styles in a custom editor. + * - Communication between VS Code and the custom editor. + * - Using CustomDocuments to store information that is shared between multiple custom editors. + * - Implementing save, undo, redo, and revert. + * - Backing up a custom editor. + */ +export class PawDrawEditorProvider implements vscode.CustomEditorProvider, vscode.CustomEditorEditingDelegate { + + public static register(context: vscode.ExtensionContext): vscode.Disposable { + return vscode.window.registerCustomEditorProvider( + PawDrawEditorProvider.viewType, + new PawDrawEditorProvider(context), + { + // For this demo extension, we enable `retainContextWhenHidden` which keeps the + // webview alive even when it is not visible. You should avoid using this setting + // unless is absolutely required as it does have memory overhead. + webviewOptions: { + retainContextWhenHidden: true, + } + }); + } + + public static readonly viewType = 'catEdit.pawDraw'; + + /** + * Map from resource to webview panels. + */ + private readonly _allWebviews = new Map>(); + + private readonly backupFolder = 'pawDraw'; + + constructor( + private readonly _context: vscode.ExtensionContext + ) { } + + // By setting an `editingDelegate`, we enable editing for our custom editor. + public readonly editingDelegate = this; + + async openCustomDocument( + uri: vscode.Uri, + _token: vscode.CancellationToken + ): Promise> { + // Check for backup first + const backupResource = this.getBackupResource(uri); + + // If we have a backup, read that. Otherwise read the resource from the workspace + let dataFile = uri; + if (backupResource && await exists(backupResource)) { + dataFile = backupResource; + } + + const fileData = await vscode.workspace.fs.readFile(dataFile); + return new PawDrawDocument(uri, fileData); + } + + async resolveCustomEditor( + document: PawDrawDocument, + webviewPanel: vscode.WebviewPanel, + _token: vscode.CancellationToken + ): Promise { + const resourceKey = document.uri.toString(); + + const webviews = this._allWebviews.get(resourceKey) || new Set(); + webviews.add(webviewPanel); + this._allWebviews.set(resourceKey, webviews); + + webviewPanel.onDidDispose(() => { + const webviews = this._allWebviews.get(resourceKey); + if (!webviews) { + return; + } + + webviews.delete(webviewPanel); + if (!webviews.size) { + this._allWebviews.delete(resourceKey) + } + }); + + // Setup initial content for the webview + webviewPanel.webview.options = { + enableScripts: true, + }; + webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview); + + webviewPanel.webview.onDidReceiveMessage(e => this.onMessage(document, e)); + + // Wait for the webview to be properly ready before we init + webviewPanel.webview.onDidReceiveMessage(e => { + if (e.type === 'ready') { + this.postMessage(webviewPanel, 'init', { + value: document.initialContent + }); + } + }); + } + + /** + * Get the static HTML used for in our editor's 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', 'pawDraw.js') + )); + const styleUri = webview.asWebviewUri(vscode.Uri.file( + path.join(this._context.extensionPath, 'media', 'pawDraw.css') + )); + + // Use a nonce to whitelist which scripts can be run + const nonce = getNonce(); + + return /* html */` + + + + + + + + + + + + + Paw Draw + + +
+ +
+ + + + + +
+ + + + `; + } + + // #region CustomEditorEditingDelegate + + private readonly _onDidEdit = new vscode.EventEmitter>(); + public readonly onDidEdit = this._onDidEdit.event; + + async save(document: PawDrawDocument, _cancellation: vscode.CancellationToken): Promise { + await this.saveAs(document, document.uri); + + // Delete backup on save + const backupResource = this.getBackupResource(document.uri); + if (backupResource) { + try { + vscode.workspace.fs.delete(backupResource) + } catch { + // noop + } + } + } + + async saveAs(document: PawDrawDocument, targetResource: vscode.Uri): Promise { + const webviews = this._allWebviews.get(document.uri.toString()); + if (!webviews || !webviews.size) { + throw new Error('Could not find webview to save for'); + } + const [panel] = webviews.values(); + const response = await this.postMessageWithResponse<{ data: number[] }>(panel, 'getFileData', {}); + const fileData = new Uint8Array(response.data); + vscode.workspace.fs.writeFile(targetResource, fileData); + } + + async applyEdits(document: PawDrawDocument, _edits: readonly PawDrawEdit[]): Promise { + this.updateWebviews(document); + } + + async undoEdits(document: PawDrawDocument, _edits: readonly PawDrawEdit[]): Promise { + this.updateWebviews(document); + } + + async revert(document: PawDrawDocument, _edits: vscode.CustomDocumentRevert): Promise { + this.updateWebviews(document); + } + + async backup(document: PawDrawDocument, _cancellation: vscode.CancellationToken): Promise { + if (!this._context.storagePath) { + return; + } + + const dir = path.join(this._context.storagePath, this.backupFolder); + await vscode.workspace.fs.createDirectory(vscode.Uri.file(dir)); + + const backupResource = this.getBackupResource(document.uri); + if (backupResource) { + await this.saveAs(document, backupResource); + } + } + + private getBackupResource(uri: vscode.Uri): vscode.Uri | undefined { + if (!this._context.storagePath) { + return undefined; + } + const dir = path.join(this._context.storagePath, this.backupFolder); + const fileName = crypto.createHash('sha256').update(uri.toString(), 'utf8').digest('hex'); + + return vscode.Uri.file(path.join(dir, fileName)); + } + + // #endregion + + public updateWebviews(document: PawDrawDocument) { + for (const webviewPanel of this._allWebviews.get(document.uri.toString()) || []) { + this.postMessage(webviewPanel, 'update', { + edits: document.appliedEdits, + }); + } + } + + private _requestId = 1; + private readonly _callbacks = new Map void>(); + + private postMessageWithResponse(panel: vscode.WebviewPanel, type: string, body: any): Promise { + const requestId = this._requestId++; + const p = new Promise(resolve => this._callbacks.set(requestId, resolve)); + panel.webview.postMessage({ type, requestId, body }); + return p; + } + + private postMessage(panel: vscode.WebviewPanel, type: string, body: any): void { + panel.webview.postMessage({ type, body }); + } + + private onMessage(document: PawDrawDocument, message: any) { + switch (message.type) { + case 'stroke': + // Tell VS Code that an edit has ocurred + this._onDidEdit.fire({ + document, + label: "Stroke", + edit: { + color: message.color, + stroke: message.stroke, + }, + }); + + // Make sure other webviews also know about this + this.updateWebviews(document); + return; + + case 'response': + const callback = this._callbacks.get(message.requestId); + if (callback) { + callback(message.body); + } + return; + } + } +} + +async function exists(backupResource: vscode.Uri): Promise { + try { + await vscode.workspace.fs.stat(backupResource); + return true; + } catch { + return false + } +} + 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/src/vscode.proposed.d.ts b/custom-editor-sample/src/vscode.proposed.d.ts new file mode 100644 index 00000000..b24a9b21 --- /dev/null +++ b/custom-editor-sample/src/vscode.proposed.d.ts @@ -0,0 +1,2092 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * This is the place for API experiments and proposals. + * These API are NOT stable and subject to change. They are only available in the Insiders + * distribution and CANNOT be used in published extensions. + * + * To test these API in local environment: + * - Use Insiders release of VS Code. + * - Add `"enableProposedApi": true` to your package.json. + * - Copy this file to your project. + */ + +declare module 'vscode' { + + // #region auth provider: https://github.com/microsoft/vscode/issues/88309 + + export interface AuthenticationSession { + id: string; + getAccessToken(): Thenable; + accountName: string; + scopes: string[] + } + + /** + * An [event](#Event) which fires when an [AuthenticationProvider](#AuthenticationProvider) is added or removed. + */ + export interface AuthenticationProvidersChangeEvent { + /** + * The ids of the [authenticationProvider](#AuthenticationProvider)s that have been added. + */ + readonly added: string[]; + + /** + * The ids of the [authenticationProvider](#AuthenticationProvider)s that have been removed. + */ + readonly removed: string[]; + } + + /** + * An [event](#Event) which fires when an [AuthenticationSession](#AuthenticationSession) is added, removed, or changed. + */ + export interface AuthenticationSessionsChangeEvent { + /** + * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been added. + */ + readonly added: string[]; + + /** + * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been removed. + */ + readonly removed: string[]; + + /** + * The ids of the [AuthenticationSession](#AuthenticationSession)s that have been changed. + */ + readonly changed: string[]; + } + + export interface AuthenticationProvider { + /** + * Used as an identifier for extensions trying to work with a particular + * provider: 'microsoft', 'github', etc. id must be unique, registering + * another provider with the same id will fail. + */ + readonly id: string; + readonly displayName: string; + + /** + * An [event](#Event) which fires when the array of sessions has changed, or data + * within a session has changed. + */ + readonly onDidChangeSessions: Event; + + /** + * Returns an array of current sessions. + */ + getSessions(): Thenable>; + + /** + * Prompts a user to login. + */ + login(scopes: string[]): Thenable; + logout(sessionId: string): Thenable; + } + + export namespace authentication { + export function registerAuthenticationProvider(provider: AuthenticationProvider): Disposable; + + /** + * Fires with the provider id that was registered or unregistered. + */ + export const onDidChangeAuthenticationProviders: Event; + + /** + * An array of the ids of authentication providers that are currently registered. + */ + export const providerIds: string[]; + + /** + * Get existing authentication sessions. Rejects if a provider with providerId is not + * registered, or if the user does not consent to sharing authentication information with + * the extension. + */ + export function getSessions(providerId: string, scopes: string[]): Thenable; + + /** + * Prompt a user to login to create a new authenticaiton session. Rejects if a provider with + * providerId is not registered, or if the user does not consent to sharing authentication + * information with the extension. + */ + export function login(providerId: string, scopes: string[]): Thenable; + + /** + * An [event](#Event) which fires when the array of sessions has changed, or data + * within a session has changed for a provider. Fires with the ids of the providers + * that have had session data change. + */ + export const onDidChangeSessions: Event<{ [providerId: string]: AuthenticationSessionsChangeEvent }>; + } + + //#endregion + + //#region Alex - resolvers + + export interface RemoteAuthorityResolverContext { + resolveAttempt: number; + } + + export class ResolvedAuthority { + readonly host: string; + readonly port: number; + + constructor(host: string, port: number); + } + + export interface ResolvedOptions { + extensionHostEnv?: { [key: string]: string | null }; + } + + export interface TunnelOptions { + remoteAddress: { port: number, host: string }; + // The desired local port. If this port can't be used, then another will be chosen. + localAddressPort?: number; + label?: string; + } + + export interface TunnelDescription { + remoteAddress: { port: number, host: string }; + //The complete local address(ex. localhost:1234) + localAddress: { port: number, host: string } | string; + } + + export interface Tunnel extends TunnelDescription { + // Implementers of Tunnel should fire onDidDispose when dispose is called. + onDidDispose: Event; + dispose(): void; + } + + /** + * Used as part of the ResolverResult if the extension has any candidate, + * published, or forwarded ports. + */ + export interface TunnelInformation { + /** + * Tunnels that are detected by the extension. The remotePort is used for display purposes. + * The localAddress should be the complete local address (ex. localhost:1234) for connecting to the port. Tunnels provided through + * detected are read-only from the forwarded ports UI. + */ + environmentTunnels?: TunnelDescription[]; + + } + + export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation; + + export class RemoteAuthorityResolverError extends Error { + static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError; + static TemporarilyNotAvailable(message?: string): RemoteAuthorityResolverError; + + constructor(message?: string); + } + + export interface RemoteAuthorityResolver { + resolve(authority: string, context: RemoteAuthorityResolverContext): ResolverResult | Thenable; + /** + * Can be optionally implemented if the extension can forward ports better than the core. + * When not implemented, the core will use its default forwarding logic. + * When implemented, the core will use this to forward ports. + */ + tunnelFactory?: (tunnelOptions: TunnelOptions) => Thenable | undefined; + + /** + * Provides filtering for candidate ports. + */ + showCandidatePort?: (host: string, port: number, detail: string) => Thenable; + } + + export namespace workspace { + /** + * Forwards a port. If the current resolver implements RemoteAuthorityResolver:forwardPort then that will be used to make the tunnel. + * By default, openTunnel only support localhost; however, RemoteAuthorityResolver:tunnelFactory can be used to support other ips. + * @param tunnelOptions The `localPort` is a suggestion only. If that port is not available another will be chosen. + */ + export function openTunnel(tunnelOptions: TunnelOptions): Thenable; + + /** + * Gets an array of the currently available tunnels. This does not include environment tunnels, only tunnels that have been created by the user. + * Note that these are of type TunnelDescription and cannot be disposed. + */ + export let tunnels: Thenable; + + /** + * Fired when the list of tunnels has changed. + */ + export const onDidChangeTunnels: Event; + } + + export interface ResourceLabelFormatter { + scheme: string; + authority?: string; + formatting: ResourceLabelFormatting; + } + + export interface ResourceLabelFormatting { + label: string; // myLabel:/${path} + // TODO@isi + // eslint-disable-next-line vscode-dts-literal-or-types + separator: '/' | '\\' | ''; + tildify?: boolean; + normalizeDriveLetter?: boolean; + workspaceSuffix?: string; + authorityPrefix?: string; + } + + export namespace workspace { + export function registerRemoteAuthorityResolver(authorityPrefix: string, resolver: RemoteAuthorityResolver): Disposable; + export function registerResourceLabelFormatter(formatter: ResourceLabelFormatter): Disposable; + } + + //#endregion + + //#region Semantic tokens: https://github.com/microsoft/vscode/issues/86415 + + export class SemanticTokensLegend { + public readonly tokenTypes: string[]; + public readonly tokenModifiers: string[]; + + constructor(tokenTypes: string[], tokenModifiers: string[]); + } + + export class SemanticTokensBuilder { + constructor(); + push(line: number, char: number, length: number, tokenType: number, tokenModifiers: number): void; + build(): Uint32Array; + } + + export class SemanticTokens { + /** + * The result id of the tokens. + * + * This is the id that will be passed to `DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits` (if implemented). + */ + readonly resultId?: string; + readonly data: Uint32Array; + + constructor(data: Uint32Array, resultId?: string); + } + + export class SemanticTokensEdits { + /** + * The result id of the tokens. + * + * This is the id that will be passed to `DocumentSemanticTokensProvider.provideDocumentSemanticTokensEdits` (if implemented). + */ + readonly resultId?: string; + readonly edits: SemanticTokensEdit[]; + + constructor(edits: SemanticTokensEdit[], resultId?: string); + } + + export class SemanticTokensEdit { + readonly start: number; + readonly deleteCount: number; + readonly data?: Uint32Array; + + constructor(start: number, deleteCount: number, data?: Uint32Array); + } + + /** + * The document semantic tokens provider interface defines the contract between extensions and + * semantic tokens. + */ + export interface DocumentSemanticTokensProvider { + /** + * An optional event to signal that the semantic tokens from this provider have changed. + */ + onDidChangeSemanticTokens?: Event; + + /** + * A file can contain many tokens, perhaps even hundreds of thousands of tokens. Therefore, to improve + * the memory consumption around describing semantic tokens, we have decided to avoid allocating an object + * for each token and we represent tokens from a file as an array of integers. Furthermore, the position + * of each token is expressed relative to the token before it because most tokens remain stable relative to + * each other when edits are made in a file. + * + * --- + * In short, each token takes 5 integers to represent, so a specific token `i` in the file consists of the following array indices: + * - at index `5*i` - `deltaLine`: token line number, relative to the previous token + * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line) + * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline. + * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes`. We currently ask that `tokenType` < 65536. + * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers` + * + * --- + * ### How to encode tokens + * + * Here is an example for encoding a file with 3 tokens in a uint32 array: + * ``` + * { line: 2, startChar: 5, length: 3, tokenType: "property", tokenModifiers: ["private", "static"] }, + * { line: 2, startChar: 10, length: 4, tokenType: "type", tokenModifiers: [] }, + * { line: 5, startChar: 2, length: 7, tokenType: "class", tokenModifiers: [] } + * ``` + * + * 1. First of all, a legend must be devised. This legend must be provided up-front and capture all possible token types. + * For this example, we will choose the following legend which must be passed in when registering the provider: + * ``` + * tokenTypes: ['property', 'type', 'class'], + * tokenModifiers: ['private', 'static'] + * ``` + * + * 2. The first transformation step is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked + * up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Multiple token modifiers can be set by using bit flags, + * so a `tokenModifier` value of `3` is first viewed as binary `0b00000011`, which means `[tokenModifiers[0], tokenModifiers[1]]` because + * bits 0 and 1 are set. Using this legend, the tokens now are: + * ``` + * { line: 2, startChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 }, + * { line: 2, startChar: 10, length: 4, tokenType: 1, tokenModifiers: 0 }, + * { line: 5, startChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 } + * ``` + * + * 3. The next step is to represent each token relative to the previous token in the file. In this case, the second token + * is on the same line as the first token, so the `startChar` of the second token is made relative to the `startChar` + * of the first token, so it will be `10 - 5`. The third token is on a different line than the second token, so the + * `startChar` of the third token will not be altered: + * ``` + * { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 }, + * { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 1, tokenModifiers: 0 }, + * { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 } + * ``` + * + * 4. Finally, the last step is to inline each of the 5 fields for a token in a single array, which is a memory friendly representation: + * ``` + * // 1st token, 2nd token, 3rd token + * [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] + * ``` + * + * *NOTE*: When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider. + * *NOTE*: If the provider cannot temporarily compute semantic tokens, it can indicate this by throwing an error with the message 'Busy'. + */ + provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): ProviderResult; + + /** + * Instead of always returning all the tokens in a file, it is possible for a `DocumentSemanticTokensProvider` to implement + * this method (`updateSemanticTokens`) and then return incremental updates to the previously provided semantic tokens. + * + * --- + * ### How tokens change when the document changes + * + * Let's look at how tokens might change. + * + * Continuing with the above example, suppose a new line was inserted at the top of the file. + * That would make all the tokens move down by one line (notice how the line has changed for each one): + * ``` + * { line: 3, startChar: 5, length: 3, tokenType: "property", tokenModifiers: ["private", "static"] }, + * { line: 3, startChar: 10, length: 4, tokenType: "type", tokenModifiers: [] }, + * { line: 6, startChar: 2, length: 7, tokenType: "class", tokenModifiers: [] } + * ``` + * The integer encoding of the tokens does not change substantially because of the delta-encoding of positions: + * ``` + * // 1st token, 2nd token, 3rd token + * [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] + * ``` + * It is possible to express these new tokens in terms of an edit applied to the previous tokens: + * ``` + * [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] // old tokens + * [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] // new tokens + * + * edit: { start: 0, deleteCount: 1, data: [3] } // replace integer at offset 0 with 3 + * ``` + * + * Furthermore, let's assume that a new token has appeared on line 4: + * ``` + * { line: 3, startChar: 5, length: 3, tokenType: "property", tokenModifiers: ["private", "static"] }, + * { line: 3, startChar: 10, length: 4, tokenType: "type", tokenModifiers: [] }, + * { line: 4, startChar: 3, length: 5, tokenType: "property", tokenModifiers: ["static"] }, + * { line: 6, startChar: 2, length: 7, tokenType: "class", tokenModifiers: [] } + * ``` + * The integer encoding of the tokens is: + * ``` + * // 1st token, 2nd token, 3rd token, 4th token + * [ 3,5,3,0,3, 0,5,4,1,0, 1,3,5,0,2, 2,2,7,2,0, ] + * ``` + * Again, it is possible to express these new tokens in terms of an edit applied to the previous tokens: + * ``` + * [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] // old tokens + * [ 3,5,3,0,3, 0,5,4,1,0, 1,3,5,0,2, 2,2,7,2,0, ] // new tokens + * + * edit: { start: 10, deleteCount: 1, data: [1,3,5,0,2,2] } // replace integer at offset 10 with [1,3,5,0,2,2] + * ``` + * + * *NOTE*: If the provider cannot compute `SemanticTokensEdits`, it can "give up" and return all the tokens in the document again. + * *NOTE*: All edits in `SemanticTokensEdits` contain indices in the old integers array, so they all refer to the previous result state. + */ + provideDocumentSemanticTokensEdits?(document: TextDocument, previousResultId: string, token: CancellationToken): ProviderResult; + } + + /** + * The document range semantic tokens provider interface defines the contract between extensions and + * semantic tokens. + */ + export interface DocumentRangeSemanticTokensProvider { + /** + * See [provideDocumentSemanticTokens](#DocumentSemanticTokensProvider.provideDocumentSemanticTokens). + */ + provideDocumentRangeSemanticTokens(document: TextDocument, range: Range, token: CancellationToken): ProviderResult; + } + + export namespace languages { + /** + * Register a semantic tokens provider for a whole document. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A document semantic tokens provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerDocumentSemanticTokensProvider(selector: DocumentSelector, provider: DocumentSemanticTokensProvider, legend: SemanticTokensLegend): Disposable; + + /** + * Register a semantic tokens provider for a document range. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A document range semantic tokens provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerDocumentRangeSemanticTokensProvider(selector: DocumentSelector, provider: DocumentRangeSemanticTokensProvider, legend: SemanticTokensLegend): Disposable; + } + + //#endregion + + //#region editor insets: https://github.com/microsoft/vscode/issues/85682 + + export interface WebviewEditorInset { + readonly editor: TextEditor; + readonly line: number; + readonly height: number; + readonly webview: Webview; + readonly onDidDispose: Event; + dispose(): void; + } + + export namespace window { + export function createWebviewTextEditorInset(editor: TextEditor, line: number, height: number, options?: WebviewOptions): WebviewEditorInset; + } + + //#endregion + + //#region read/write in chunks: https://github.com/microsoft/vscode/issues/84515 + + export interface FileSystemProvider { + open?(resource: Uri, options: { create: boolean }): number | Thenable; + close?(fd: number): void | Thenable; + read?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): number | Thenable; + write?(fd: number, pos: number, data: Uint8Array, offset: number, length: number): number | Thenable; + } + + //#endregion + + //#region TextSearchProvider: https://github.com/microsoft/vscode/issues/59921 + + /** + * The parameters of a query for text search. + */ + export interface TextSearchQuery { + /** + * The text pattern to search for. + */ + pattern: string; + + /** + * Whether or not `pattern` should match multiple lines of text. + */ + isMultiline?: boolean; + + /** + * Whether or not `pattern` should be interpreted as a regular expression. + */ + isRegExp?: boolean; + + /** + * Whether or not the search should be case-sensitive. + */ + isCaseSensitive?: boolean; + + /** + * Whether or not to search for whole word matches only. + */ + isWordMatch?: boolean; + } + + /** + * A file glob pattern to match file paths against. + * TODO@roblou - merge this with the GlobPattern docs/definition in vscode.d.ts. + * @see [GlobPattern](#GlobPattern) + */ + export type GlobString = string; + + /** + * Options common to file and text search + */ + export interface SearchOptions { + /** + * The root folder to search within. + */ + folder: Uri; + + /** + * Files that match an `includes` glob pattern should be included in the search. + */ + includes: GlobString[]; + + /** + * Files that match an `excludes` glob pattern should be excluded from the search. + */ + excludes: GlobString[]; + + /** + * Whether external files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useIgnoreFiles"`. + */ + useIgnoreFiles: boolean; + + /** + * Whether symlinks should be followed while searching. + * See the vscode setting `"search.followSymlinks"`. + */ + followSymlinks: boolean; + + /** + * Whether global files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useGlobalIgnoreFiles"`. + */ + useGlobalIgnoreFiles: boolean; + } + + /** + * Options to specify the size of the result text preview. + * These options don't affect the size of the match itself, just the amount of preview text. + */ + export interface TextSearchPreviewOptions { + /** + * The maximum number of lines in the preview. + * Only search providers that support multiline search will ever return more than one line in the match. + */ + matchLines: number; + + /** + * The maximum number of characters included per line. + */ + charsPerLine: number; + } + + /** + * Options that apply to text search. + */ + export interface TextSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults: number; + + /** + * Options to specify the size of the result text preview. + */ + previewOptions?: TextSearchPreviewOptions; + + /** + * Exclude files larger than `maxFileSize` in bytes. + */ + maxFileSize?: number; + + /** + * Interpret files using this encoding. + * See the vscode setting `"files.encoding"` + */ + encoding?: string; + + /** + * Number of lines of context to include before each match. + */ + beforeContext?: number; + + /** + * Number of lines of context to include after each match. + */ + afterContext?: number; + } + + /** + * Information collected when text search is complete. + */ + export interface TextSearchComplete { + /** + * Whether the search hit the limit on the maximum number of search results. + * `maxResults` on [`TextSearchOptions`](#TextSearchOptions) specifies the max number of results. + * - If exactly that number of matches exist, this should be false. + * - If `maxResults` matches are returned and more exist, this should be true. + * - If search hits an internal limit which is less than `maxResults`, this should be true. + */ + limitHit?: boolean; + } + + /** + * A preview of the text result. + */ + export interface TextSearchMatchPreview { + /** + * The matching lines of text, or a portion of the matching line that contains the match. + */ + text: string; + + /** + * The Range within `text` corresponding to the text of the match. + * The number of matches must match the TextSearchMatch's range property. + */ + matches: Range | Range[]; + } + + /** + * A match from a text search + */ + export interface TextSearchMatch { + /** + * The uri for the matching document. + */ + uri: Uri; + + /** + * The range of the match within the document, or multiple ranges for multiple matches. + */ + ranges: Range | Range[]; + + /** + * A preview of the text match. + */ + preview: TextSearchMatchPreview; + } + + /** + * A line of context surrounding a TextSearchMatch. + */ + export interface TextSearchContext { + /** + * The uri for the matching document. + */ + uri: Uri; + + /** + * One line of text. + * previewOptions.charsPerLine applies to this + */ + text: string; + + /** + * The line number of this line of context. + */ + lineNumber: number; + } + + export type TextSearchResult = TextSearchMatch | TextSearchContext; + + /** + * A TextSearchProvider provides search results for text results inside files in the workspace. + */ + export interface TextSearchProvider { + /** + * Provide results that match the given text pattern. + * @param query The parameters for this query. + * @param options A set of options to consider while searching. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult; + } + + //#endregion + + //#region FileSearchProvider: https://github.com/microsoft/vscode/issues/73524 + + /** + * The parameters of a query for file search. + */ + export interface FileSearchQuery { + /** + * The search pattern to match against file paths. + */ + pattern: string; + } + + /** + * Options that apply to file search. + */ + export interface FileSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults?: number; + + /** + * A CancellationToken that represents the session for this search query. If the provider chooses to, this object can be used as the key for a cache, + * and searches with the same session object can search the same cache. When the token is cancelled, the session is complete and the cache can be cleared. + */ + session?: CancellationToken; + } + + /** + * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions. + * + * A FileSearchProvider is the more powerful of two ways to implement file search in VS Code. Use a FileSearchProvider if you wish to search within a folder for + * all files that match the user's query. + * + * The FileSearchProvider will be invoked on every keypress in quickopen. When `workspace.findFiles` is called, it will be invoked with an empty query string, + * and in that case, every file in the folder should be returned. + */ + export interface FileSearchProvider { + /** + * Provide the set of files that match a certain file path pattern. + * @param query The parameters for this query. + * @param options A set of options to consider while searching files. + * @param token A cancellation token. + */ + provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, token: CancellationToken): ProviderResult; + } + + export namespace workspace { + /** + * Register a search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerFileSearchProvider(scheme: string, provider: FileSearchProvider): Disposable; + + /** + * Register a text search provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable; + } + + //#endregion + + //#region findTextInFiles: https://github.com/microsoft/vscode/issues/59924 + + /** + * Options that can be set on a findTextInFiles search. + */ + export interface FindTextInFilesOptions { + /** + * A [glob pattern](#GlobPattern) that defines the files to search for. The glob pattern + * will be matched against the file paths of files relative to their workspace. Use a [relative pattern](#RelativePattern) + * to restrict the search results to a [workspace folder](#WorkspaceFolder). + */ + include?: GlobPattern; + + /** + * A [glob pattern](#GlobPattern) that defines files and folders to exclude. The glob pattern + * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will + * apply. + */ + exclude?: GlobPattern; + + /** + * Whether to use the default and user-configured excludes. Defaults to true. + */ + useDefaultExcludes?: boolean; + + /** + * The maximum number of results to search for + */ + maxResults?: number; + + /** + * Whether external files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useIgnoreFiles"`. + */ + useIgnoreFiles?: boolean; + + /** + * Whether global files that exclude files, like .gitignore, should be respected. + * See the vscode setting `"search.useGlobalIgnoreFiles"`. + */ + useGlobalIgnoreFiles?: boolean; + + /** + * Whether symlinks should be followed while searching. + * See the vscode setting `"search.followSymlinks"`. + */ + followSymlinks?: boolean; + + /** + * Interpret files using this encoding. + * See the vscode setting `"files.encoding"` + */ + encoding?: string; + + /** + * Options to specify the size of the result text preview. + */ + previewOptions?: TextSearchPreviewOptions; + + /** + * Number of lines of context to include before each match. + */ + beforeContext?: number; + + /** + * Number of lines of context to include after each match. + */ + afterContext?: number; + } + + export namespace workspace { + /** + * Search text in files across all [workspace folders](#workspace.workspaceFolders) in the workspace. + * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. + * @param callback A callback, called for each result + * @param token A token that can be used to signal cancellation to the underlying search engine. + * @return A thenable that resolves when the search is complete. + */ + export function findTextInFiles(query: TextSearchQuery, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable; + + /** + * Search text in files across all [workspace folders](#workspace.workspaceFolders) in the workspace. + * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. + * @param options An optional set of query options. Include and exclude patterns, maxResults, etc. + * @param callback A callback, called for each result + * @param token A token that can be used to signal cancellation to the underlying search engine. + * @return A thenable that resolves when the search is complete. + */ + export function findTextInFiles(query: TextSearchQuery, options: FindTextInFilesOptions, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable; + } + + //#endregion + + //#region diff command: https://github.com/microsoft/vscode/issues/84899 + + /** + * The contiguous set of modified lines in a diff. + */ + export interface LineChange { + readonly originalStartLineNumber: number; + readonly originalEndLineNumber: number; + readonly modifiedStartLineNumber: number; + readonly modifiedEndLineNumber: number; + } + + export namespace commands { + + /** + * Registers a diff information command that can be invoked via a keyboard shortcut, + * a menu item, an action, or directly. + * + * Diff information commands are different from ordinary [commands](#commands.registerCommand) as + * they only execute when there is an active diff editor when the command is called, and the diff + * information has been computed. Also, the command handler of an editor command has access to + * the diff information. + * + * @param command A unique identifier for the command. + * @param callback A command handler function with access to the [diff information](#LineChange). + * @param thisArg The `this` context used when invoking the handler function. + * @return Disposable which unregisters this command on disposal. + */ + export function registerDiffInformationCommand(command: string, callback: (diff: LineChange[], ...args: any[]) => any, thisArg?: any): Disposable; + } + + //#endregion + + //#region file-decorations: https://github.com/microsoft/vscode/issues/54938 + + export class Decoration { + letter?: string; + title?: string; + color?: ThemeColor; + priority?: number; + bubble?: boolean; + } + + export interface DecorationProvider { + onDidChangeDecorations: Event; + provideDecoration(uri: Uri, token: CancellationToken): ProviderResult; + } + + export namespace window { + export function registerDecorationProvider(provider: DecorationProvider): Disposable; + } + + //#endregion + + //#region deprecated debug API + + export interface DebugConfigurationProvider { + /** + * Deprecated, use DebugAdapterDescriptorFactory.provideDebugAdapter instead. + * @deprecated Use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead + */ + debugAdapterExecutable?(folder: WorkspaceFolder | undefined, token?: CancellationToken): ProviderResult; + } + + //#endregion + + //#region LogLevel: https://github.com/microsoft/vscode/issues/85992 + + /** + * The severity level of a log message + */ + export enum LogLevel { + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, + Critical = 6, + Off = 7 + } + + export namespace env { + /** + * Current logging level. + */ + export const logLevel: LogLevel; + + /** + * An [event](#Event) that fires when the log level has changed. + */ + export const onDidChangeLogLevel: Event; + } + + //#endregion + + //#region Joao: SCM validation + + /** + * Represents the validation type of the Source Control input. + */ + export enum SourceControlInputBoxValidationType { + + /** + * Something not allowed by the rules of a language or other means. + */ + Error = 0, + + /** + * Something suspicious but allowed. + */ + Warning = 1, + + /** + * Something to inform about but not a problem. + */ + Information = 2 + } + + export interface SourceControlInputBoxValidation { + + /** + * The validation message to display. + */ + readonly message: string; + + /** + * The validation type. + */ + readonly type: SourceControlInputBoxValidationType; + } + + /** + * Represents the input box in the Source Control viewlet. + */ + export interface SourceControlInputBox { + + /** + * A validation function for the input box. It's possible to change + * the validation provider simply by setting this property to a different function. + */ + validateInput?(value: string, cursorPosition: number): ProviderResult; + } + + //#endregion + + //#region Joao: SCM selected provider + + export interface SourceControl { + + /** + * Whether the source control is selected. + */ + readonly selected: boolean; + + /** + * An event signaling when the selection state changes. + */ + readonly onDidChangeSelection: Event; + } + + //#endregion + + //#region Joao: SCM Input Box + + /** + * Represents the input box in the Source Control viewlet. + */ + export interface SourceControlInputBox { + + /** + * Controls whether the input box is visible (default is `true`). + */ + visible: boolean; + } + + //#endregion + + //#region Terminal data write event https://github.com/microsoft/vscode/issues/78502 + + export interface TerminalDataWriteEvent { + /** + * The [terminal](#Terminal) for which the data was written. + */ + readonly terminal: Terminal; + /** + * The data being written. + */ + readonly data: string; + } + + namespace window { + /** + * An event which fires when the terminal's pty slave pseudo-device is written to. In other + * words, this provides access to the raw data stream from the process running within the + * terminal, including VT sequences. + */ + export const onDidWriteTerminalData: Event; + } + + //#endregion + + //#region Terminal dimensions property and change event https://github.com/microsoft/vscode/issues/55718 + + /** + * An [event](#Event) which fires when a [Terminal](#Terminal)'s dimensions change. + */ + export interface TerminalDimensionsChangeEvent { + /** + * The [terminal](#Terminal) for which the dimensions have changed. + */ + readonly terminal: Terminal; + /** + * The new value for the [terminal's dimensions](#Terminal.dimensions). + */ + readonly dimensions: TerminalDimensions; + } + + namespace window { + /** + * An event which fires when the [dimensions](#Terminal.dimensions) of the terminal change. + */ + export const onDidChangeTerminalDimensions: Event; + } + + export interface Terminal { + /** + * The current dimensions of the terminal. This will be `undefined` immediately after the + * terminal is created as the dimensions are not known until shortly after the terminal is + * created. + */ + readonly dimensions: TerminalDimensions | undefined; + } + + //#endregion + + //#region Terminal link handlers https://github.com/microsoft/vscode/issues/91606 + + export namespace window { + export function registerTerminalLinkHandler(handler: TerminalLinkHandler): Disposable; + } + + export interface TerminalLinkHandler { + /** + * @return true when the link was handled (and should not be considered by + * other providers including the default), false when the link was not handled. + */ + handleLink(terminal: Terminal, link: string): ProviderResult; + } + + //#endregion + + //#region Joh -> exclusive document filters + + export interface DocumentFilter { + exclusive?: boolean; + } + + //#endregion + + //#region Alex - OnEnter enhancement + export interface OnEnterRule { + /** + * This rule will only execute if the text above the this line matches this regular expression. + */ + oneLineAboveText?: RegExp; + } + //#endregion + + //#region Tree View: https://github.com/microsoft/vscode/issues/61313 + /** + * Label describing the [Tree item](#TreeItem) + */ + export interface TreeItemLabel { + + /** + * A human-readable string describing the [Tree item](#TreeItem). + */ + label: string; + + /** + * Ranges in the label to highlight. A range is defined as a tuple of two number where the + * first is the inclusive start index and the second the exclusive end index + */ + highlights?: [number, number][]; + + } + + export class TreeItem2 extends TreeItem { + /** + * Label describing this item. When `falsy`, it is derived from [resourceUri](#TreeItem.resourceUri). + */ + label?: string | TreeItemLabel | /* for compilation */ any; + + /** + * @param label Label describing this item + * @param collapsibleState [TreeItemCollapsibleState](#TreeItemCollapsibleState) of the tree item. Default is [TreeItemCollapsibleState.None](#TreeItemCollapsibleState.None) + */ + constructor(label: TreeItemLabel, collapsibleState?: TreeItemCollapsibleState); + } + //#endregion + + //#region CustomExecution: https://github.com/microsoft/vscode/issues/81007 + /** + * A task to execute + */ + export class Task2 extends Task { + detail?: string; + } + + export class CustomExecution2 extends CustomExecution { + /** + * Constructs a CustomExecution task object. The callback will be executed the task is run, at which point the + * extension should return the Pseudoterminal it will "run in". The task should wait to do further execution until + * [Pseudoterminal.open](#Pseudoterminal.open) is called. Task cancellation should be handled using + * [Pseudoterminal.close](#Pseudoterminal.close). When the task is complete fire + * [Pseudoterminal.onDidClose](#Pseudoterminal.onDidClose). + * @param callback The callback that will be called when the task is started by a user. + */ + constructor(callback: (resolvedDefinition?: TaskDefinition) => Thenable); + } + //#endregion + + //#region Task presentation group: https://github.com/microsoft/vscode/issues/47265 + export interface TaskPresentationOptions { + /** + * Controls whether the task is executed in a specific terminal group using split panes. + */ + group?: string; + } + //#endregion + + //#region Status bar item with ID and Name: https://github.com/microsoft/vscode/issues/74972 + + export namespace window { + + /** + * Options to configure the status bar item. + */ + export interface StatusBarItemOptions { + + /** + * A unique identifier of the status bar item. The identifier + * is for example used to allow a user to show or hide the + * status bar item in the UI. + */ + id: string; + + /** + * A human readable name of the status bar item. The name is + * for example used as a label in the UI to show or hide the + * status bar item. + */ + name: string; + + /** + * The alignment of the status bar item. + */ + alignment?: StatusBarAlignment; + + /** + * The priority of the status bar item. Higher value means the item should + * be shown more to the left. + */ + priority?: number; + } + + /** + * Creates a status bar [item](#StatusBarItem). + * + * @param options The options of the item. If not provided, some default values + * will be assumed. For example, the `StatusBarItemOptions.id` will be the id + * of the extension and the `StatusBarItemOptions.name` will be the extension name. + * @return A new status bar item. + */ + export function createStatusBarItem(options?: StatusBarItemOptions): StatusBarItem; + } + + //#endregion + + //#region OnTypeRename: https://github.com/microsoft/vscode/issues/88424 + + /** + * The rename provider interface defines the contract between extensions and + * the live-rename feature. + */ + export interface OnTypeRenameProvider { + /** + * Provide a list of ranges that can be live renamed together. + * + * @param document The document in which the command was invoked. + * @param position The position at which the command was invoked. + * @param token A cancellation token. + * @return A list of ranges that can be live-renamed togehter. The ranges must have + * identical length and contain identical text content. The ranges cannot overlap. + */ + provideOnTypeRenameRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + } + + namespace languages { + /** + * Register a rename provider that works on type. + * + * Multiple providers can be registered for a language. In that case providers are sorted + * by their [score](#languages.match) and the best-matching provider is used. Failure + * of the selected provider will cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider An on type rename provider. + * @param stopPattern Stop on type renaming when input text matches the regular expression. Defaults to `^\s`. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerOnTypeRenameProvider(selector: DocumentSelector, provider: OnTypeRenameProvider, stopPattern?: RegExp): Disposable; + } + + //#endregion + + //#region Custom editors: https://github.com/microsoft/vscode/issues/77131 + + /** + * Defines the editing capability of a custom webview editor. This allows the webview editor to hook into standard + * editor events such as `undo` or `save`. + * + * @param EditType Type of edits used for the documents this delegate handles. + */ + interface CustomEditorEditingDelegate { + /** + * Save the resource. + * + * @param document Document to save. + * @param cancellation Token that signals the save is no longer required (for example, if another save was triggered). + * + * @return Thenable signaling that the save has completed. + */ + save(document: CustomDocument, cancellation: CancellationToken): Thenable; + + /** + * Save the existing resource at a new path. + * + * @param document Document to save. + * @param targetResource Location to save to. + * + * @return Thenable signaling that the save has completed. + */ + saveAs(document: CustomDocument, targetResource: Uri): Thenable; + + /** + * Event triggered by extensions to signal to VS Code that an edit has occurred. + */ + readonly onDidEdit: Event>; + + /** + * Apply a set of edits. + * + * Note that is not invoked when `onDidEdit` is called because `onDidEdit` implies also updating the view to reflect the edit. + * + * @param document Document to apply edits to. + * @param edit Array of edits. Sorted from oldest to most recent. + * + * @return Thenable signaling that the change has completed. + */ + applyEdits(document: CustomDocument, edits: readonly EditType[]): Thenable; + + /** + * Undo a set of edits. + * + * This is triggered when a user undoes an edit. + * + * @param document Document to undo edits from. + * @param edit Array of edits. Sorted from most recent to oldest. + * + * @return Thenable signaling that the change has completed. + */ + undoEdits(document: CustomDocument, edits: readonly EditType[]): Thenable; + + /** + * Revert the file to its last saved state. + * + * @param document Document to revert. + * @param edits Added or applied edits. + * + * @return Thenable signaling that the change has completed. + */ + revert(document: CustomDocument, edits: CustomDocumentRevert): Thenable; + + /** + * Back up the resource in its current state. + * + * Backups are used for hot exit and to prevent data loss. Your `backup` method should persist the resource in + * its current state, i.e. with the edits applied. Most commonly this means saving the resource to disk in + * the `ExtensionContext.storagePath`. When VS Code reloads and your custom editor is opened for a resource, + * your extension should first check to see if any backups exist for the resource. If there is a backup, your + * extension should load the file contents from there instead of from the resource in the workspace. + * + * `backup` is triggered whenever an edit it made. Calls to `backup` are debounced so that if multiple edits are + * made in quick succession, `backup` is only triggered after the last one. `backup` is not invoked when + * `auto save` is enabled (since auto save already persists resource ). + * + * @param document Document to revert. + * @param cancellation Token that signals the current backup since a new backup is coming in. It is up to your + * extension to decided how to respond to cancellation. If for example your extension is backing up a large file + * in an operation that takes time to complete, your extension may decide to finish the ongoing backup rather + * than cancelling it to ensure that VS Code has some valid backup. + */ + backup(document: CustomDocument, cancellation: CancellationToken): Thenable; + } + + /** + * Event triggered by extensions to signal to VS Code that an edit has occurred on a `CustomDocument`. + * + * @param EditType Type of edits used for the document. + */ + interface CustomDocumentEditEvent { + /** + * Document the edit is for. + */ + readonly document: CustomDocument; + + /** + * Object that describes the edit. + * + * Edit objects are passed back to your extension in `CustomEditorEditingDelegate.undoEdits`, + * `CustomEditorEditingDelegate.applyEdits`, and `CustomEditorEditingDelegate.revert`. + */ + readonly edit: EditType; + + /** + * Display name describing the edit. + */ + readonly label?: string; + } + + /** + * Data about a revert for a `CustomDocument`. + */ + interface CustomDocumentRevert { + /** + * List of edits that were undone to get the document back to its on disk state. + */ + readonly undoneEdits: readonly EditType[]; + + /** + * List of edits that were reapplied to get the document back to its on disk state. + */ + readonly appliedEdits: readonly EditType[]; + } + + /** + * Represents a custom document used by a `CustomEditorProvider`. + * + * All custom documents must subclass `CustomDocument`. Custom documents are only used within a given + * `CustomEditorProvider`. The lifecycle of a `CustomDocument` is managed by VS Code. When no more references + * remain to a `CustomDocument`, it is disposed of. + * + * @param EditType Type of edits used in this document. + */ + class CustomDocument { + /** + * @param viewType The associated uri for this document. + * @param uri The associated viewType for this document. + */ + constructor(viewType: string, uri: Uri); + + /** + * The associated viewType for this document. + */ + readonly viewType: string; + + /** + * The associated uri for this document. + */ + readonly uri: Uri; + + /** + * Event fired when there are no more references to the `CustomDocument`. + */ + readonly onDidDispose: Event; + + /** + * List of edits from document open to the document's current state. + */ + readonly appliedEdits: ReadonlyArray; + + /** + * List of edits from document open to the document's last saved point. + * + * The save point will be behind `appliedEdits` if the user saves and then continues editing, + * or in front of the last entry in `appliedEdits` if the user saves and then hits undo. + */ + readonly savedEdits: ReadonlyArray; + } + + /** + * Provider for webview editors that use a custom data model. + * + * Custom webview editors use [`CustomDocument`](#CustomDocument) as their data model. + * This gives extensions full control over actions such as edit, save, and backup. + * + * You should use custom text based editors when dealing with binary files or more complex scenarios. For simple text + * based documents, use [`WebviewTextEditorProvider`](#WebviewTextEditorProvider) instead. + */ + export interface CustomEditorProvider { + + /** + * Resolve the model for a given resource. + * + * `resolveCustomDocument` is called when the first editor for a given resource is opened, and the resolve document + * is passed to `resolveCustomEditor`. The resolved `CustomDocument` is re-used for subsequent editor opens. + * If all editors for a given resource are closed, the `CustomDocument` is disposed of. Opening an editor at + * this point will trigger another call to `resolveCustomDocument`. + * + * @param uri Uri of the document to open. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return The custom document. + */ + openCustomDocument(uri: Uri, token: CancellationToken): Thenable>; + + /** + * Resolve a webview editor for a given resource. + * + * This is called when a user first opens a resource for a `CustomEditorProvider`, or if they reopen an + * existing editor using this `CustomEditorProvider`. + * + * To resolve a webview editor, the provider must fill in its initial html content and hook up all + * the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later, + * for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details + * + * @param document Document for the resource being resolved. + * @param webviewPanel Webview to resolve. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return Thenable indicating that the webview editor has been resolved. + */ + resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel, token: CancellationToken): Thenable; + + /** + * Defines the editing capability of a custom webview document. + * + * When not provided, the document is considered readonly. + */ + readonly editingDelegate?: CustomEditorEditingDelegate; + } + + /** + * Provider for text based webview editors. + * + * Text based webview editors use a [`TextDocument`](#TextDocument) as their data model. This considerably simplifies + * implementing a webview editor as it allows VS Code to handle many common operations such as + * undo and backup. The provider is responsible for synchronizing text changes between the webview and the `TextDocument`. + * + * You should use text based webview editors when dealing with text based file formats, such as `xml` or `json`. + * For binary files or more specialized use cases, see [CustomEditorProvider](#CustomEditorProvider). + */ + export interface CustomTextEditorProvider { + + /** + * Resolve a webview editor for a given text resource. + * + * This is called when a user first opens a resource for a `CustomTextEditorProvider`, or if they reopen an + * existing editor using this `CustomTextEditorProvider`. + * + * To resolve a webview editor, the provider must fill in its initial html content and hook up all + * the event listeners it is interested it. The provider can also hold onto the `WebviewPanel` to use later, + * for example in a command. See [`WebviewPanel`](#WebviewPanel) for additional details. + * + * @param document Document for the resource to resolve. + * @param webviewPanel Webview to resolve. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return Thenable indicating that the webview editor has been resolved. + */ + resolveCustomTextEditor(document: TextDocument, webviewPanel: WebviewPanel, token: CancellationToken): Thenable; + + /** + * TODO: discuss this at api sync. + * + * Handle when the underlying resource for a custom editor is renamed. + * + * This allows the webview for the editor be preserved throughout the rename. If this method is not implemented, + * VS Code will destory the previous custom editor and create a replacement one. + * + * @param newDocument New text document to use for the custom editor. + * @param existingWebviewPanel Webview panel for the custom editor. + * @param token A cancellation token that indicates the result is no longer needed. + * + * @return Thenable indicating that the webview editor has been moved. + */ + moveCustomTextEditor?(newDocument: TextDocument, existingWebviewPanel: WebviewPanel, token: CancellationToken): Thenable; + } + + namespace window { + /** + * Register a new provider for a custom editor. + * + * @param viewType Type of the webview editor provider. This should match the `viewType` from the + * `package.json` contributions. + * @param provider Provider that resolves editors. + * @param options Options for the provider + * + * @return Disposable that unregisters the provider. + */ + export function registerCustomEditorProvider( + viewType: string, + provider: CustomEditorProvider | CustomTextEditorProvider, + options?: { + readonly webviewOptions?: WebviewPanelOptions; + } + ): Disposable; + } + + //#endregion + + + //#region allow QuickPicks to skip sorting: https://github.com/microsoft/vscode/issues/73904 + + export interface QuickPick extends QuickInput { + /** + * An optional flag to sort the final results by index of first query match in label. Defaults to true. + */ + sortByLabel: boolean; + } + + //#endregion + + //#region Allow theme icons in hovers: https://github.com/microsoft/vscode/issues/84695 + + export interface MarkdownString { + + /** + * Indicates that this markdown string can contain [ThemeIcons](#ThemeIcon), e.g. `$(zap)`. + */ + readonly supportThemeIcons?: boolean; + } + + //#endregion + + //#region Peng: Notebook + + export enum CellKind { + Markdown = 1, + Code = 2 + } + + export enum CellOutputKind { + Text = 1, + Error = 2, + Rich = 3 + } + + export interface CellStreamOutput { + outputKind: CellOutputKind.Text; + text: string; + } + + export interface CellErrorOutput { + outputKind: CellOutputKind.Error; + /** + * Exception Name + */ + ename: string; + /** + * Exception Value + */ + evalue: string; + /** + * Exception call stack + */ + traceback: string[]; + } + + export interface CellDisplayOutput { + outputKind: CellOutputKind.Rich; + /** + * { mime_type: value } + * + * Example: + * ```json + * { + * "outputKind": vscode.CellOutputKind.Rich, + * "data": { + * "text/html": [ + * "

Hello

" + * ], + * "text/plain": [ + * "" + * ] + * } + * } + */ + data: { [key: string]: any }; + } + + export type CellOutput = CellStreamOutput | CellErrorOutput | CellDisplayOutput; + + export interface NotebookCellMetadata { + editable: boolean; + } + + export interface NotebookCell { + readonly uri: Uri; + handle: number; + language: string; + cellKind: CellKind; + outputs: CellOutput[]; + getContent(): string; + metadata?: NotebookCellMetadata; + } + + export interface NotebookDocumentMetadata { + editable: boolean; + } + + export interface NotebookDocument { + readonly uri: Uri; + readonly fileName: string; + readonly isDirty: boolean; + languages: string[]; + cells: NotebookCell[]; + displayOrder?: GlobPattern[]; + metadata?: NotebookDocumentMetadata; + } + + export interface NotebookEditor { + readonly document: NotebookDocument; + viewColumn?: ViewColumn; + /** + * Fired when the output hosting webview posts a message. + */ + readonly onDidReceiveMessage: Event; + /** + * Post a message to the output hosting webview. + * + * Messages are only delivered if the editor is live. + * + * @param message Body of the message. This must be a string or other json serilizable object. + */ + postMessage(message: any): Thenable; + + /** + * Create a notebook cell. The cell is not inserted into current document when created. Extensions should insert the cell into the document by [TextDocument.cells](#TextDocument.cells) + */ + createCell(content: string, language: string, type: CellKind, outputs: CellOutput[], metadata: NotebookCellMetadata): NotebookCell; + } + + export interface NotebookProvider { + resolveNotebook(editor: NotebookEditor): Promise; + executeCell(document: NotebookDocument, cell: NotebookCell | undefined): Promise; + save(document: NotebookDocument): Promise; + } + + export interface NotebookOutputSelector { + type: string; + subTypes?: string[]; + } + + export interface NotebookOutputRenderer { + /** + * + * @returns HTML fragment. We can probably return `CellOutput` instead of string ? + * + */ + render(document: NotebookDocument, cell: NotebookCell, output: CellOutput, mimeType: string): string; + preloads?: Uri[]; + } + + namespace window { + export function registerNotebookProvider( + notebookType: string, + provider: NotebookProvider + ): Disposable; + + export function registerNotebookOutputRenderer(type: string, outputSelector: NotebookOutputSelector, renderer: NotebookOutputRenderer): Disposable; + + export let activeNotebookDocument: NotebookDocument | undefined; + } + + //#endregion + + //#region color theme access + + /** + * Represents a color theme kind. + */ + export enum ColorThemeKind { + Light = 1, + Dark = 2, + HighContrast = 3 + } + + /** + * Represents a color theme. + */ + export interface ColorTheme { + + /** + * The kind of this color theme: light, dark or high contrast. + */ + readonly kind: ColorThemeKind; + } + + export namespace window { + /** + * The currently active color theme as configured in the settings. The active + * theme can be changed via the `workbench.colorTheme` setting. + */ + export let activeColorTheme: ColorTheme; + + /** + * An [event](#Event) which fires when the active theme changes or one of it's colors chnage. + */ + export const onDidChangeActiveColorTheme: Event; + } + + //#endregion + + + //#region https://github.com/microsoft/vscode/issues/39441 + + export interface CompletionItem { + /** + * Will be merged into CompletionItem#label + */ + label2?: CompletionItemLabel; + } + + export interface CompletionItemLabel { + /** + * The function or variable. Rendered leftmost. + */ + name: string; + + /** + * The signature without the return type. Render after `name`. + */ + signature?: string; + + /** + * The fully qualified name, like package name or file path. Rendered after `signature`. + */ + qualifier?: string; + + /** + * The return-type of a function or type of a property/variable. Rendered rightmost. + */ + type?: string; + } + + //#endregion + + + //#region eamodio - timeline: https://github.com/microsoft/vscode/issues/84297 + + export class TimelineItem { + /** + * A timestamp (in milliseconds since 1 January 1970 00:00:00) for when the timeline item occurred. + */ + timestamp: number; + + /** + * A human-readable string describing the timeline item. + */ + label: string; + + /** + * Optional id for the timeline item. It must be unique across all the timeline items provided by this source. + * + * If not provided, an id is generated using the timeline item's timestamp. + */ + id?: string; + + /** + * The icon path or [ThemeIcon](#ThemeIcon) for the timeline item. + */ + iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon; + + /** + * A human readable string describing less prominent details of the timeline item. + */ + description?: string; + + /** + * The tooltip text when you hover over the timeline item. + */ + detail?: string; + + /** + * The [command](#Command) that should be executed when the timeline item is selected. + */ + command?: Command; + + /** + * Context value of the timeline item. This can be used to contribute specific actions to the item. + * For example, a timeline item is given a context value as `commit`. When contributing actions to `timeline/item/context` + * using `menus` extension point, you can specify context value for key `timelineItem` in `when` expression like `timelineItem == commit`. + * ``` + * "contributes": { + * "menus": { + * "timeline/item/context": [ + * { + * "command": "extension.copyCommitId", + * "when": "timelineItem == commit" + * } + * ] + * } + * } + * ``` + * This will show the `extension.copyCommitId` action only for items where `contextValue` is `commit`. + */ + contextValue?: string; + + /** + * @param label A human-readable string describing the timeline item + * @param timestamp A timestamp (in milliseconds since 1 January 1970 00:00:00) for when the timeline item occurred + */ + constructor(label: string, timestamp: number); + } + + export interface TimelineChangeEvent { + /** + * The [uri](#Uri) of the resource for which the timeline changed. + * If the [uri](#Uri) is `undefined` that signals that the timeline source for all resources changed. + */ + uri?: Uri; + + /** + * A flag which indicates whether the entire timeline should be reset. + */ + reset?: boolean; + } + + export interface Timeline { + readonly paging?: { + /** + * A provider-defined cursor specifying the starting point of timeline items which are after the ones returned. + * Use `undefined` to signal that there are no more items to be returned. + */ + readonly cursor: string | undefined; + } + + /** + * An array of [timeline items](#TimelineItem). + */ + readonly items: readonly TimelineItem[]; + } + + export interface TimelineOptions { + /** + * A provider-defined cursor specifying the starting point of the timeline items that should be returned. + */ + cursor?: string; + + /** + * An optional maximum number timeline items or the all timeline items newer (inclusive) than the timestamp or id that should be returned. + * If `undefined` all timeline items should be returned. + */ + limit?: number | { timestamp: number; id?: string }; + } + + export interface TimelineProvider { + /** + * An optional event to signal that the timeline for a source has changed. + * To signal that the timeline for all resources (uris) has changed, do not pass any argument or pass `undefined`. + */ + onDidChange?: Event; + + /** + * An identifier of the source of the timeline items. This can be used to filter sources. + */ + readonly id: string; + + /** + * A human-readable string describing the source of the timeline items. This can be used as the display label when filtering sources. + */ + readonly label: string; + + /** + * Provide [timeline items](#TimelineItem) for a [Uri](#Uri). + * + * @param uri The [uri](#Uri) of the file to provide the timeline for. + * @param options A set of options to determine how results should be returned. + * @param token A cancellation token. + * @return The [timeline result](#TimelineResult) or a thenable that resolves to such. The lack of a result + * can be signaled by returning `undefined`, `null`, or an empty array. + */ + provideTimeline(uri: Uri, options: TimelineOptions, token: CancellationToken): ProviderResult; + } + + export namespace workspace { + /** + * Register a timeline provider. + * + * Multiple providers can be registered. In that case, providers are asked in + * parallel and the results are merged. A failing provider (rejected promise or exception) will + * not cause a failure of the whole operation. + * + * @param scheme A scheme or schemes that defines which documents this provider is applicable to. Can be `*` to target all documents. + * @param provider A timeline provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerTimelineProvider(scheme: string | string[], provider: TimelineProvider): Disposable; + } + + //#endregion + + + //#region https://github.com/microsoft/vscode/issues/90208 + + export interface ExtensionContext { + /** + * Get the uri of a resource contained in the extension. + * + * @param relativePath A relative path to a resource contained in the extension. + * @return The uri of the resource. + */ + asExtensionUri(relativePath: string): Uri; + + /** + * + */ + readonly extensionUri: Uri; + } + + export interface Extension { + /** + * Get the uri of a resource contained in the extension. + * + * @param relativePath A relative path to a resource contained in the extension. + * @return The uri of the resource. + */ + asExtensionUri(relativePath: string): Uri; + + /** + * + */ + readonly extensionUri: Uri; + } + + //#endregion + + //#region https://github.com/microsoft/vscode/issues/86788 + + export interface CodeActionProviderMetadata { + /** + * Static documentation for a class of code actions. + * + * The documentation is shown in the code actions menu if either: + * + * - Code actions of `kind` are requested by VS Code. In this case, VS Code will show the documentation that + * most closely matches the requested code action kind. For example, if a provider has documentation for + * both `Refactor` and `RefactorExtract`, when the user requests code actions for `RefactorExtract`, + * VS Code will use the documentation for `RefactorExtract` intead of the documentation for `Refactor`. + * + * - Any code actions of `kind` are returned by the provider. + */ + readonly documentation?: ReadonlyArray<{ readonly kind: CodeActionKind, readonly command: Command }>; + } + + //#endregion + + //#region Dialog title: https://github.com/microsoft/vscode/issues/82871 + + /** + * Options to configure the behaviour of a file open dialog. + * + * * Note 1: A dialog can select files, folders, or both. This is not true for Windows + * which enforces to open either files or folder, but *not both*. + * * Note 2: Explicitly setting `canSelectFiles` and `canSelectFolders` to `false` is futile + * and the editor then silently adjusts the options to select files. + */ + export interface OpenDialogOptions { + /** + * Dialog title. + * + * Depending on the underlying operating system this parameter might be ignored, since some + * systems do not present title on open dialogs. + */ + title?: string; + } + + /** + * Options to configure the behaviour of a file save dialog. + */ + export interface SaveDialogOptions { + /** + * Dialog title. + * + * Depending on the underlying operating system this parameter might be ignored, since some + * systems do not present title on save dialogs. + */ + title?: string; + } + + //#endregion + + //#region https://github.com/microsoft/vscode/issues/90208 + + export namespace Uri { + + /** + * + * @param base + * @param pathFragments + * @returns A new uri + */ + export function joinPaths(base: Uri, ...pathFragments: string[]): Uri; + } + + //#endregion + + //#region https://github.com/microsoft/vscode/issues/91541 + + export enum CompletionItemKind { + User = 25, + Issue = 26, + } + + //#endregion + +} diff --git a/custom-editor-sample/tsconfig.json b/custom-editor-sample/tsconfig.json new file mode 100644 index 00000000..4ce52747 --- /dev/null +++ b/custom-editor-sample/tsconfig.json @@ -0,0 +1,11 @@ +{ + "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 diff --git a/webview-sample/media/sand.jpg b/webview-sample/media/sand.jpg new file mode 100644 index 00000000..6b86e9ec Binary files /dev/null and b/webview-sample/media/sand.jpg differ