92 | );
93 | };
94 |
95 | const Result: Component<{result: CustomTextSearchMatch, workspace: vscode.WorkspaceFolder[] | null}> = (props) => {
96 | return (
97 |
98 | {(range, index) => }
99 |
100 | );
101 | };
102 |
103 | export default Result;
104 |
--------------------------------------------------------------------------------
/solid-app/src/importPatterns.ts:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | name: 'snippet import',
4 | fileDefinition: {
5 | folder: 'snippets',
6 | extension: 'liquid',
7 | },
8 | conditions: [
9 | {
10 | regex: (filename: string) => `(render|include)\\s*('|")${filename}('|")`,
11 | include: '**/**.liquid',
12 | },
13 | ],
14 | },
15 | {
16 | name: 'section import',
17 | fileDefinition: {
18 | folder: 'sections',
19 | extension: 'liquid',
20 | },
21 | conditions: [
22 | {
23 | regex: (filename: string) => `section\\s*('|")${filename}('|")`,
24 | include: '**/**.liquid',
25 | },
26 | {
27 | regex: (filename: string) => `"type": "${filename}"`,
28 | include: '**/templates/**.json',
29 | },
30 | ],
31 | },
32 | {
33 | name: 'section group import',
34 | fileDefinition: {
35 | folder: 'sections',
36 | extension: 'json',
37 | },
38 | conditions: [
39 | {
40 | regex: (filename: string) => `sections\\s*('|")${filename}('|")`,
41 | include: '**/**.liquid',
42 | }
43 | ],
44 | },
45 | {
46 | name: 'asset import',
47 | fileDefinition: {
48 | folder: 'assets',
49 | },
50 | conditions: [
51 | {
52 | regex: (filename: string) =>
53 | `('|")${filename}(?\\..*|)('|")\\s*\\|\\s*asset_url`,
54 | customFilenameFunc: (path: string, fullFilename: string, filename: string) =>
55 | fullFilename.replace('.liquid', ''),
56 | include: '**/**.liquid',
57 | },
58 | ],
59 | },
60 | ];
61 |
--------------------------------------------------------------------------------
/solid-app/src/index.css:
--------------------------------------------------------------------------------
1 | .vscode-icon {
2 | font-family: 'seti';
3 | font-size: 20px;
4 | }
5 | body {
6 | padding: 0;
7 | }
8 |
--------------------------------------------------------------------------------
/solid-app/src/index.tsx:
--------------------------------------------------------------------------------
1 | /* @refresh reload */
2 | import { render } from 'solid-js/web';
3 |
4 | import './index.css';
5 | import App from './App';
6 | import { VscodeSetiIconData } from './types';
7 |
8 | declare global {
9 | interface Window {
10 | acquireVsCodeApi: Function;
11 | vscode: { postMessage: Function };
12 | ['vs-seti-icon-theme']: VscodeSetiIconData;
13 | }
14 | }
15 |
16 | render(() => , document.getElementById('root') as HTMLElement);
17 |
--------------------------------------------------------------------------------
/solid-app/src/toolkit.d.ts:
--------------------------------------------------------------------------------
1 | import "solid-js";
2 |
3 | // An important part of getting the Webview UI Toolkit to work with
4 | // Solid + TypeScript + JSX is to extend the solid-js JSX.IntrinsicElements
5 | // type interface to include type annotations for each of the toolkit's components.
6 | //
7 | // Without this, type errors will occur when you try to use any toolkit component
8 | // in your Solid + TypeScript + JSX component code. (Note that this file shouldn't be
9 | // necessary if you're not using TypeScript or are using tagged template literals
10 | // instead of JSX for your Solid component code).
11 | //
12 | // Important: This file should be updated whenever a new component is added to the
13 | // toolkit. You can find a list of currently available toolkit components here:
14 | //
15 | // https://github.com/microsoft/vscode-webview-ui-toolkit/blob/main/docs/components.md
16 | //
17 | declare module "solid-js" {
18 | namespace JSX {
19 | interface IntrinsicElements {
20 | "vscode-badge": any;
21 | "vscode-button": any;
22 | "vscode-checkbox": any;
23 | "vscode-data-grid": any;
24 | "vscode-divider": any;
25 | "vscode-dropdown": any;
26 | "vscode-link": any;
27 | "vscode-option": any;
28 | "vscode-panels": any;
29 | "vscode-panel-tab": any;
30 | "vscode-panel-view": any;
31 | "vscode-progress-ring": any;
32 | "vscode-radio": any;
33 | "vscode-radio-group": any;
34 | "vscode-tag": any;
35 | "vscode-text-area": any;
36 | "vscode-text-field": any;
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/solid-app/src/types.d.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | interface VscodeSetiIconData {
4 | file: string;
5 | fileExtensions: {
6 | [key: string]: string;
7 | }
8 | iconDefinitions: {
9 | [key: string]: {
10 | fontCharacter: string;
11 | fontColor: string;
12 | };
13 | }
14 | languageIds: {
15 | [key: string]: string;
16 | }
17 | }
18 |
19 | interface FileDefinition {
20 | folder: string;
21 | extension?: string;
22 | }
23 |
24 | interface LinePosition {
25 | line: number;
26 | character: number;
27 | }
28 |
29 | type Range = [LinePosition, LinePosition];
30 |
31 | interface CustomTextSearchMatch {
32 | uri: vscode.Uri;
33 | outOfUris?: vscode.Uri[];
34 | ranges: Range[];
35 | preview: {
36 | matches: Range[];
37 | text: string;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/solid-app/src/utils.tsx:
--------------------------------------------------------------------------------
1 | import { CustomTextSearchMatch } from "./types";
2 |
3 | export const countResults = (results: CustomTextSearchMatch[]) => {
4 | return results.reduce((accumulator, current) => {
5 | if (!current.outOfUris) return accumulator + current.ranges.length;
6 | return accumulator + current.outOfUris.filter(entry => entry !== null).length
7 | }, 0);
8 | };
--------------------------------------------------------------------------------
/solid-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "target": "ESNext",
5 | "module": "ESNext",
6 | "moduleResolution": "node",
7 | "allowSyntheticDefaultImports": true,
8 | "esModuleInterop": true,
9 | "jsx": "preserve",
10 | "jsxImportSource": "solid-js",
11 | "types": ["vite/client"],
12 | "noEmit": true,
13 | "isolatedModules": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/solid-app/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import solidPlugin from 'vite-plugin-solid';
3 | import path from 'node:path';
4 |
5 | export default defineConfig({
6 | plugins: [solidPlugin()],
7 | base: 'http://localhost:PORT/',
8 | build: {
9 | watch: {},
10 | target: 'esnext',
11 | }
12 | });
13 |
--------------------------------------------------------------------------------
/solid-app/vscode.proposed.textSearchProvider.d.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | declare module 'vscode' {
7 |
8 | // https://github.com/microsoft/vscode/issues/59921
9 |
10 | /**
11 | * The parameters of a query for text search.
12 | */
13 | export interface TextSearchQuery {
14 | /**
15 | * The text pattern to search for.
16 | */
17 | pattern: string;
18 |
19 | /**
20 | * Whether or not `pattern` should match multiple lines of text.
21 | */
22 | isMultiline?: boolean;
23 |
24 | /**
25 | * Whether or not `pattern` should be interpreted as a regular expression.
26 | */
27 | isRegExp?: boolean;
28 |
29 | /**
30 | * Whether or not the search should be case-sensitive.
31 | */
32 | isCaseSensitive?: boolean;
33 |
34 | /**
35 | * Whether or not to search for whole word matches only.
36 | */
37 | isWordMatch?: boolean;
38 | }
39 |
40 | /**
41 | * A file glob pattern to match file paths against.
42 | * TODO@roblourens merge this with the GlobPattern docs/definition in vscode.d.ts.
43 | * @see {@link GlobPattern}
44 | */
45 | export type GlobString = string;
46 |
47 | /**
48 | * Options common to file and text search
49 | */
50 | export interface SearchOptions {
51 | /**
52 | * The root folder to search within.
53 | */
54 | folder: Uri;
55 |
56 | /**
57 | * Files that match an `includes` glob pattern should be included in the search.
58 | */
59 | includes: GlobString[];
60 |
61 | /**
62 | * Files that match an `excludes` glob pattern should be excluded from the search.
63 | */
64 | excludes: GlobString[];
65 |
66 | /**
67 | * Whether external files that exclude files, like .gitignore, should be respected.
68 | * See the vscode setting `"search.useIgnoreFiles"`.
69 | */
70 | useIgnoreFiles: boolean;
71 |
72 | /**
73 | * Whether symlinks should be followed while searching.
74 | * See the vscode setting `"search.followSymlinks"`.
75 | */
76 | followSymlinks: boolean;
77 |
78 | /**
79 | * Whether global files that exclude files, like .gitignore, should be respected.
80 | * See the vscode setting `"search.useGlobalIgnoreFiles"`.
81 | */
82 | useGlobalIgnoreFiles: boolean;
83 |
84 | /**
85 | * Whether files in parent directories that exclude files, like .gitignore, should be respected.
86 | * See the vscode setting `"search.useParentIgnoreFiles"`.
87 | */
88 | useParentIgnoreFiles: boolean;
89 | }
90 |
91 | /**
92 | * Options to specify the size of the result text preview.
93 | * These options don't affect the size of the match itself, just the amount of preview text.
94 | */
95 | export interface TextSearchPreviewOptions {
96 | /**
97 | * The maximum number of lines in the preview.
98 | * Only search providers that support multiline search will ever return more than one line in the match.
99 | */
100 | matchLines: number;
101 |
102 | /**
103 | * The maximum number of characters included per line.
104 | */
105 | charsPerLine: number;
106 | }
107 |
108 | /**
109 | * Options that apply to text search.
110 | */
111 | export interface TextSearchOptions extends SearchOptions {
112 | /**
113 | * The maximum number of results to be returned.
114 | */
115 | maxResults: number;
116 |
117 | /**
118 | * Options to specify the size of the result text preview.
119 | */
120 | previewOptions?: TextSearchPreviewOptions;
121 |
122 | /**
123 | * Exclude files larger than `maxFileSize` in bytes.
124 | */
125 | maxFileSize?: number;
126 |
127 | /**
128 | * Interpret files using this encoding.
129 | * See the vscode setting `"files.encoding"`
130 | */
131 | encoding?: string;
132 |
133 | /**
134 | * Number of lines of context to include before each match.
135 | */
136 | beforeContext?: number;
137 |
138 | /**
139 | * Number of lines of context to include after each match.
140 | */
141 | afterContext?: number;
142 | }
143 |
144 | /**
145 | * Represents the severity of a TextSearchComplete message.
146 | */
147 | export enum TextSearchCompleteMessageType {
148 | Information = 1,
149 | Warning = 2,
150 | }
151 |
152 | /**
153 | * A message regarding a completed search.
154 | */
155 | export interface TextSearchCompleteMessage {
156 | /**
157 | * Markdown text of the message.
158 | */
159 | text: string;
160 | /**
161 | * Whether the source of the message is trusted, command links are disabled for untrusted message sources.
162 | * Messaged are untrusted by default.
163 | */
164 | trusted?: boolean;
165 | /**
166 | * The message type, this affects how the message will be rendered.
167 | */
168 | type: TextSearchCompleteMessageType;
169 | }
170 |
171 | /**
172 | * Information collected when text search is complete.
173 | */
174 | export interface TextSearchComplete {
175 | /**
176 | * Whether the search hit the limit on the maximum number of search results.
177 | * `maxResults` on {@linkcode TextSearchOptions} specifies the max number of results.
178 | * - If exactly that number of matches exist, this should be false.
179 | * - If `maxResults` matches are returned and more exist, this should be true.
180 | * - If search hits an internal limit which is less than `maxResults`, this should be true.
181 | */
182 | limitHit?: boolean;
183 |
184 | /**
185 | * Additional information regarding the state of the completed search.
186 | *
187 | * Messages with "Information" style support links in markdown syntax:
188 | * - Click to [run a command](command:workbench.action.OpenQuickPick)
189 | * - Click to [open a website](https://aka.ms)
190 | *
191 | * Commands may optionally return { triggerSearch: true } to signal to the editor that the original search should run be again.
192 | */
193 | message?: TextSearchCompleteMessage | TextSearchCompleteMessage[];
194 | }
195 |
196 | /**
197 | * A preview of the text result.
198 | */
199 | export interface TextSearchMatchPreview {
200 | /**
201 | * The matching lines of text, or a portion of the matching line that contains the match.
202 | */
203 | text: string;
204 |
205 | /**
206 | * The Range within `text` corresponding to the text of the match.
207 | * The number of matches must match the TextSearchMatch's range property.
208 | */
209 | matches: Range | Range[];
210 | }
211 |
212 | /**
213 | * A match from a text search
214 | */
215 | export interface TextSearchMatch {
216 | /**
217 | * The uri for the matching document.
218 | */
219 | uri: Uri;
220 |
221 | /**
222 | * The range of the match within the document, or multiple ranges for multiple matches.
223 | */
224 | ranges: Range | Range[];
225 |
226 | /**
227 | * A preview of the text match.
228 | */
229 | preview: TextSearchMatchPreview;
230 | }
231 |
232 | /**
233 | * A line of context surrounding a TextSearchMatch.
234 | */
235 | export interface TextSearchContext {
236 | /**
237 | * The uri for the matching document.
238 | */
239 | uri: Uri;
240 |
241 | /**
242 | * One line of text.
243 | * previewOptions.charsPerLine applies to this
244 | */
245 | text: string;
246 |
247 | /**
248 | * The line number of this line of context.
249 | */
250 | lineNumber: number;
251 | }
252 |
253 | export type TextSearchResult = TextSearchMatch | TextSearchContext;
254 |
255 | /**
256 | * A TextSearchProvider provides search results for text results inside files in the workspace.
257 | */
258 | export interface TextSearchProvider {
259 | /**
260 | * Provide results that match the given text pattern.
261 | * @param query The parameters for this query.
262 | * @param options A set of options to consider while searching.
263 | * @param progress A progress callback that must be invoked for all results.
264 | * @param token A cancellation token.
265 | */
266 | provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult;
267 | }
268 |
269 | export namespace workspace {
270 | /**
271 | * Register a text search provider.
272 | *
273 | * Only one provider can be registered per scheme.
274 | *
275 | * @param scheme The provider will be invoked for workspace folders that have this file scheme.
276 | * @param provider The provider.
277 | * @return A {@link Disposable} that unregisters this provider when being disposed.
278 | */
279 | export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable;
280 | }
281 | }
282 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | // The module 'vscode' contains the VS Code extensibility API
2 | // Import the module and reference it with the alias vscode in your code below
3 | import * as vscode from 'vscode';
4 | import * as fs from 'fs';
5 | import * as path from 'path';
6 | import express from 'express';
7 | import cors from 'cors';
8 | import * as stringSimilarity from 'string-similarity';
9 | import { CustomTextSearchMatch } from './types';
10 |
11 | const app = express();
12 | const port = (vscode.workspace.getConfiguration('code-sense').get('serverPort') || 49168) as number;
13 | console.log('PORT', port);
14 |
15 | app.use(cors());
16 | app.use(express.static(path.join(__dirname, '../solid-app/dist/')));
17 |
18 | app
19 | .listen(port, () => {
20 | console.log(`Example app listening on port ${port}`);
21 | })
22 | .on('error', (err) => {
23 | vscode.window.showErrorMessage(`Code Sense: ${err.message}`);
24 | });
25 |
26 | export function activate(context: vscode.ExtensionContext) {
27 | // Track currently webview panel
28 | let currentPanel: vscode.WebviewPanel | undefined = undefined;
29 |
30 | const disposable = vscode.commands.registerCommand('code-sense.start', async () => {
31 | // const fullWebServerUri = await vscode.env.asExternalUri(
32 | // vscode.Uri.parse(`http://localhost:5173`)
33 | // );
34 |
35 | // console.log(fullWebServerUri);
36 |
37 | if (currentPanel) return currentPanel.reveal();
38 | let activeEditor = vscode.window.activeTextEditor;
39 | let firstSet = true;
40 |
41 | const setActiveEditor = (editor: vscode.TextEditor | undefined) => {
42 | if (editor && (editor.document.fileName !== activeEditor?.document.fileName || firstSet)) {
43 | firstSet = false;
44 | activeEditor = editor;
45 | currentPanel?.webview.postMessage({ activeEditor: editor });
46 | }
47 | };
48 |
49 | // Create and show a new webview
50 | currentPanel = vscode.window.createWebviewPanel(
51 | 'code-sense', // Identifies the type of the webview. Used internally
52 | 'Code Sense', // Title of the panel displayed to the user
53 | {
54 | viewColumn: activeEditor?.viewColumn ? activeEditor?.viewColumn + 1 : vscode.ViewColumn.Two, // Editor column to show the new webview panel in.
55 | preserveFocus: true,
56 | },
57 | {
58 | enableScripts: true,
59 | retainContextWhenHidden: true,
60 | localResourceRoots: [vscode.Uri.joinPath(context.extensionUri, 'solid-app/dist')],
61 | } // Webview options. More on these later.
62 | );
63 |
64 | currentPanel.iconPath = vscode.Uri.file(
65 | path.join(context.extensionPath, 'images/Code-Sense-Logo.png')
66 | );
67 |
68 | const entryPath = path.join(context.extensionPath, 'solid-app/dist/index.html');
69 |
70 | const loadEntry = () => {
71 | const fileBuffer = fs.readFileSync(entryPath);
72 | let file = fileBuffer.toString();
73 |
74 | const reloadedBadge = `
75 |
Reloaded
76 |
100 | `;
101 |
102 | file = file.replace('', `\n${reloadedBadge}`);
103 |
104 | file = file.replaceAll('PORT', port.toString());
105 |
106 | if (currentPanel) currentPanel.webview.html = file;
107 |
108 | console.log('Entry loaded');
109 | };
110 |
111 | loadEntry();
112 |
113 | fs.watchFile(entryPath, loadEntry);
114 |
115 | // async function checkIfFileExists(uri: vscode.Uri) {
116 | // try {
117 | // // Attempt to get the file stats
118 | // await vscode.workspace.fs.stat(uri);
119 | // // If the stat method succeeds, the file exists
120 | // console.log('The file exists.');
121 | // return true;
122 | // } catch (error) {
123 | // // If the stat method fails, it's likely because the file does not exist
124 | // console.log('The file does not exist.');
125 | // return false;
126 | // }
127 | // }
128 |
129 | currentPanel.webview.onDidReceiveMessage(async (message) => {
130 | console.log('RECEIVED MESSAGE', message);
131 |
132 | switch (message.command) {
133 | case 'find':
134 | let promises: Promise[] = [];
135 | vscode.workspace
136 | .findTextInFiles(
137 | message.textSearchQuery,
138 | message.textSearchOptions,
139 | async (result) => {
140 | if (message.for === 'outof' && 'preview' in result) {
141 | const matches = Array.from(
142 | result.preview.text.matchAll(message.textSearchQuery.pattern)
143 | );
144 |
145 | (result as CustomTextSearchMatch).outOfUris = [];
146 | promises = matches.map(async (match, index) => {
147 | const filename = match.groups?.filename;
148 | const extension: string =
149 | match.groups?.extension ||
150 | (message.fileDefinition?.extension
151 | ? '.' + message.fileDefinition.extension
152 | : '');
153 | if (filename) {
154 | const path = `${message.fileDefinition.folder}/${filename}${extension}`;
155 | const uris = await vscode.workspace.findFiles(`**/${path}*`, '', 3);
156 |
157 | let uri: vscode.Uri | null;
158 | if (uris.length === 0 || activeEditor === undefined) uri = null;
159 | else if (uris.length === 1) uri = uris[0];
160 | else {
161 | const activePath = activeEditor.document.uri.path;
162 | uri = uris.reduce((prev, curr) => {
163 | const prevScore = stringSimilarity.compareTwoStrings(
164 | activePath,
165 | prev.path
166 | );
167 | const currScore = stringSimilarity.compareTwoStrings(
168 | activePath,
169 | curr.path
170 | );
171 | return prevScore > currScore ? prev : curr;
172 | });
173 | }
174 | (result as CustomTextSearchMatch).outOfUris[index] = uri;
175 | }
176 | });
177 |
178 | await Promise.allSettled(promises);
179 | }
180 | currentPanel?.webview.postMessage({
181 | result,
182 | for: message.for,
183 | activeEditor: message.activeEditor,
184 | });
185 | },
186 | undefined
187 | )
188 | .then(async (finalResult) => {
189 | await Promise.allSettled(promises);
190 |
191 | currentPanel?.webview.postMessage({
192 | result: finalResult,
193 | for: message.for,
194 | activeEditor: message.activeEditor,
195 | done: true,
196 | });
197 | });
198 | break;
199 | case 'openFile':
200 | const document = await vscode.workspace.openTextDocument(message.path);
201 |
202 | vscode.window.showTextDocument(document, 1).then((editor) => {
203 | if (message.range) {
204 | const range = new vscode.Range(
205 | message.range[0].line,
206 | message.range[0].character,
207 | message.range[1].line,
208 | message.range[1].character
209 | );
210 | editor.revealRange(range, vscode.TextEditorRevealType.InCenter);
211 | const decorationType = vscode.window.createTextEditorDecorationType({
212 | border: '2px solid var(--vscode-editor-findMatchBorder)',
213 | });
214 | editor.setDecorations(decorationType, [range]);
215 | const disposable = vscode.window.onDidChangeTextEditorSelection(() => {
216 | decorationType.dispose();
217 | disposable.dispose();
218 | });
219 | }
220 | });
221 |
222 | // const uri = vscode.Uri.parse(message.path);
223 | // console.log('uri', uri);
224 | // try {
225 |
226 | // } catch (e)
227 | // console.log(message.path);
228 | // vscode.commands.executeCommand('workbench.action.quickOpen', message.path);
229 | break;
230 | case 'getWorkspace':
231 | currentPanel?.webview.postMessage({ workspace: vscode.workspace.workspaceFolders });
232 | break;
233 | case 'getActiveEditor':
234 | setActiveEditor(vscode.window.activeTextEditor);
235 | break;
236 | }
237 | });
238 |
239 | // Listens for active editor changes
240 | const disposable2 = vscode.window.onDidChangeActiveTextEditor((editor) =>
241 | setActiveEditor(editor)
242 | );
243 |
244 | currentPanel.onDidDispose(
245 | () => {
246 | currentPanel = undefined;
247 | fs.unwatchFile(entryPath, loadEntry);
248 | disposable2.dispose();
249 | },
250 | null,
251 | context.subscriptions
252 | );
253 | });
254 |
255 | context.subscriptions.push(disposable);
256 | }
257 |
258 | // this method is called when your extension is deactivated
259 | export function deactivate() {}
260 |
--------------------------------------------------------------------------------
/src/test/runTest.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 |
3 | import { runTests } from '@vscode/test-electron';
4 |
5 | async function main() {
6 | try {
7 | // The folder containing the Extension Manifest package.json
8 | // Passed to `--extensionDevelopmentPath`
9 | const extensionDevelopmentPath = path.resolve(__dirname, '../../');
10 |
11 | // The path to test runner
12 | // Passed to --extensionTestsPath
13 | const extensionTestsPath = path.resolve(__dirname, './suite/index');
14 |
15 | // Download VS Code, unzip it and run the integration test
16 | await runTests({ extensionDevelopmentPath, extensionTestsPath });
17 | } catch (err) {
18 | console.error('Failed to run tests');
19 | process.exit(1);
20 | }
21 | }
22 |
23 | main();
24 |
--------------------------------------------------------------------------------
/src/test/suite/extension.test.ts:
--------------------------------------------------------------------------------
1 | import * as assert from 'assert';
2 |
3 | // You can import and use all API from the 'vscode' module
4 | // as well as import your extension to test it
5 | import * as vscode from 'vscode';
6 | // import * as myExtension from '../../extension';
7 |
8 | suite('Extension Test Suite', () => {
9 | vscode.window.showInformationMessage('Start all tests.');
10 |
11 | test('Sample test', () => {
12 | assert.strictEqual(-1, [1, 2, 3].indexOf(5));
13 | assert.strictEqual(-1, [1, 2, 3].indexOf(0));
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/src/test/suite/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as Mocha from 'mocha';
3 | import * as glob from 'glob';
4 |
5 | export function run(): Promise {
6 | // Create the mocha test
7 | const mocha = new Mocha({
8 | ui: 'tdd',
9 | color: true
10 | });
11 |
12 | const testsRoot = path.resolve(__dirname, '..');
13 |
14 | return new Promise((c, e) => {
15 | glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
16 | if (err) {
17 | return e(err);
18 | }
19 |
20 | // Add files to the test suite
21 | files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
22 |
23 | try {
24 | // Run the mocha test
25 | mocha.run(failures => {
26 | if (failures > 0) {
27 | e(new Error(`${failures} tests failed.`));
28 | } else {
29 | c();
30 | }
31 | });
32 | } catch (err) {
33 | console.error(err);
34 | e(err);
35 | }
36 | });
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/src/types.d.ts:
--------------------------------------------------------------------------------
1 | import * as vscode from 'vscode';
2 |
3 | interface FileDefinition {
4 | folder: string;
5 | extension?: string;
6 | }
7 |
8 | interface CustomTextSearchMatch extends vscode.TextSearchMatch {
9 | outOfUris: (vscode.Uri | null)[];
10 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "ES2022",
5 | "outDir": "out",
6 | "esModuleInterop": true,
7 | "lib": [
8 | "ES2022"
9 | ],
10 | "sourceMap": true,
11 | "rootDir": "src",
12 | "strict": true /* enable all strict type-checking options */
13 |
14 | /* Additional Checks */
15 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
16 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
17 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
18 | },
19 | "include": ["src", "./vscode.proposed.findTextInFiles.d.ts", "./vscode.proposed.textSearchProvider.d.ts"],
20 | }
21 |
--------------------------------------------------------------------------------
/vsc-extension-quickstart.md:
--------------------------------------------------------------------------------
1 | # Welcome to your VS Code Extension
2 |
3 | ## What's in the folder
4 |
5 | * This folder contains all of the files necessary for your extension.
6 | * `package.json` - this is the manifest file in which you declare your extension and command.
7 | * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin.
8 | * `src/extension.ts` - this is the main file where you will provide the implementation of your command.
9 | * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`.
10 | * We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
11 |
12 | ## Get up and running straight away
13 |
14 | * Press `F5` to open a new window with your extension loaded.
15 | * Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
16 | * Set breakpoints in your code inside `src/extension.ts` to debug your extension.
17 | * Find output from your extension in the debug console.
18 |
19 | ## Make changes
20 |
21 | * You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
22 | * You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
23 |
24 |
25 | ## Explore the API
26 |
27 | * You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
28 |
29 | ## Run tests
30 |
31 | * Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`.
32 | * Press `F5` to run the tests in a new window with your extension loaded.
33 | * See the output of the test result in the debug console.
34 | * Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder.
35 | * The provided test runner will only consider files matching the name pattern `**.test.ts`.
36 | * You can create folders inside the `test` folder to structure your tests any way you want.
37 |
38 | ## Go further
39 |
40 | * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
41 | * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace.
42 | * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
43 |
--------------------------------------------------------------------------------
/vscode.proposed.findTextInFiles.d.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | declare module 'vscode' {
7 |
8 | // https://github.com/microsoft/vscode/issues/59924
9 |
10 | /**
11 | * Options that can be set on a findTextInFiles search.
12 | */
13 | export interface FindTextInFilesOptions {
14 | /**
15 | * A {@link GlobPattern glob pattern} that defines the files to search for. The glob pattern
16 | * will be matched against the file paths of files relative to their workspace. Use a {@link RelativePattern relative pattern}
17 | * to restrict the search results to a {@link WorkspaceFolder workspace folder}.
18 | */
19 | include?: GlobPattern;
20 |
21 | /**
22 | * A {@link GlobPattern glob pattern} that defines files and folders to exclude. The glob pattern
23 | * will be matched against the file paths of resulting matches relative to their workspace. When `undefined`, default excludes will
24 | * apply.
25 | */
26 | exclude?: GlobPattern;
27 |
28 | /**
29 | * Whether to use the default and user-configured excludes. Defaults to true.
30 | */
31 | useDefaultExcludes?: boolean;
32 |
33 | /**
34 | * The maximum number of results to search for
35 | */
36 | maxResults?: number;
37 |
38 | /**
39 | * Whether external files that exclude files, like .gitignore, should be respected.
40 | * See the vscode setting `"search.useIgnoreFiles"`.
41 | */
42 | useIgnoreFiles?: boolean;
43 |
44 | /**
45 | * Whether global files that exclude files, like .gitignore, should be respected.
46 | * See the vscode setting `"search.useGlobalIgnoreFiles"`.
47 | */
48 | useGlobalIgnoreFiles?: boolean;
49 |
50 | /**
51 | * Whether files in parent directories that exclude files, like .gitignore, should be respected.
52 | * See the vscode setting `"search.useParentIgnoreFiles"`.
53 | */
54 | useParentIgnoreFiles?: boolean;
55 |
56 | /**
57 | * Whether symlinks should be followed while searching.
58 | * See the vscode setting `"search.followSymlinks"`.
59 | */
60 | followSymlinks?: boolean;
61 |
62 | /**
63 | * Interpret files using this encoding.
64 | * See the vscode setting `"files.encoding"`
65 | */
66 | encoding?: string;
67 |
68 | /**
69 | * Options to specify the size of the result text preview.
70 | */
71 | previewOptions?: TextSearchPreviewOptions;
72 |
73 | /**
74 | * Number of lines of context to include before each match.
75 | */
76 | beforeContext?: number;
77 |
78 | /**
79 | * Number of lines of context to include after each match.
80 | */
81 | afterContext?: number;
82 | }
83 |
84 | export namespace workspace {
85 | /**
86 | * Search text in files across all {@link workspace.workspaceFolders workspace folders} in the workspace.
87 | * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words.
88 | * @param callback A callback, called for each result
89 | * @param token A token that can be used to signal cancellation to the underlying search engine.
90 | * @return A thenable that resolves when the search is complete.
91 | */
92 | export function findTextInFiles(query: TextSearchQuery, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable;
93 |
94 | /**
95 | * Search text in files across all {@link workspace.workspaceFolders workspace folders} in the workspace.
96 | * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words.
97 | * @param options An optional set of query options. Include and exclude patterns, maxResults, etc.
98 | * @param callback A callback, called for each result
99 | * @param token A token that can be used to signal cancellation to the underlying search engine.
100 | * @return A thenable that resolves when the search is complete.
101 | */
102 | export function findTextInFiles(query: TextSearchQuery, options: FindTextInFilesOptions, callback: (result: TextSearchResult) => void, token?: CancellationToken): Thenable;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/vscode.proposed.textSearchProvider.d.ts:
--------------------------------------------------------------------------------
1 | /*---------------------------------------------------------------------------------------------
2 | * Copyright (c) Microsoft Corporation. All rights reserved.
3 | * Licensed under the MIT License. See License.txt in the project root for license information.
4 | *--------------------------------------------------------------------------------------------*/
5 |
6 | declare module 'vscode' {
7 |
8 | // https://github.com/microsoft/vscode/issues/59921
9 |
10 | /**
11 | * The parameters of a query for text search.
12 | */
13 | export interface TextSearchQuery {
14 | /**
15 | * The text pattern to search for.
16 | */
17 | pattern: string;
18 |
19 | /**
20 | * Whether or not `pattern` should match multiple lines of text.
21 | */
22 | isMultiline?: boolean;
23 |
24 | /**
25 | * Whether or not `pattern` should be interpreted as a regular expression.
26 | */
27 | isRegExp?: boolean;
28 |
29 | /**
30 | * Whether or not the search should be case-sensitive.
31 | */
32 | isCaseSensitive?: boolean;
33 |
34 | /**
35 | * Whether or not to search for whole word matches only.
36 | */
37 | isWordMatch?: boolean;
38 | }
39 |
40 | /**
41 | * A file glob pattern to match file paths against.
42 | * TODO@roblourens merge this with the GlobPattern docs/definition in vscode.d.ts.
43 | * @see {@link GlobPattern}
44 | */
45 | export type GlobString = string;
46 |
47 | /**
48 | * Options common to file and text search
49 | */
50 | export interface SearchOptions {
51 | /**
52 | * The root folder to search within.
53 | */
54 | folder: Uri;
55 |
56 | /**
57 | * Files that match an `includes` glob pattern should be included in the search.
58 | */
59 | includes: GlobString[];
60 |
61 | /**
62 | * Files that match an `excludes` glob pattern should be excluded from the search.
63 | */
64 | excludes: GlobString[];
65 |
66 | /**
67 | * Whether external files that exclude files, like .gitignore, should be respected.
68 | * See the vscode setting `"search.useIgnoreFiles"`.
69 | */
70 | useIgnoreFiles: boolean;
71 |
72 | /**
73 | * Whether symlinks should be followed while searching.
74 | * See the vscode setting `"search.followSymlinks"`.
75 | */
76 | followSymlinks: boolean;
77 |
78 | /**
79 | * Whether global files that exclude files, like .gitignore, should be respected.
80 | * See the vscode setting `"search.useGlobalIgnoreFiles"`.
81 | */
82 | useGlobalIgnoreFiles: boolean;
83 |
84 | /**
85 | * Whether files in parent directories that exclude files, like .gitignore, should be respected.
86 | * See the vscode setting `"search.useParentIgnoreFiles"`.
87 | */
88 | useParentIgnoreFiles: boolean;
89 | }
90 |
91 | /**
92 | * Options to specify the size of the result text preview.
93 | * These options don't affect the size of the match itself, just the amount of preview text.
94 | */
95 | export interface TextSearchPreviewOptions {
96 | /**
97 | * The maximum number of lines in the preview.
98 | * Only search providers that support multiline search will ever return more than one line in the match.
99 | */
100 | matchLines: number;
101 |
102 | /**
103 | * The maximum number of characters included per line.
104 | */
105 | charsPerLine: number;
106 | }
107 |
108 | /**
109 | * Options that apply to text search.
110 | */
111 | export interface TextSearchOptions extends SearchOptions {
112 | /**
113 | * The maximum number of results to be returned.
114 | */
115 | maxResults: number;
116 |
117 | /**
118 | * Options to specify the size of the result text preview.
119 | */
120 | previewOptions?: TextSearchPreviewOptions;
121 |
122 | /**
123 | * Exclude files larger than `maxFileSize` in bytes.
124 | */
125 | maxFileSize?: number;
126 |
127 | /**
128 | * Interpret files using this encoding.
129 | * See the vscode setting `"files.encoding"`
130 | */
131 | encoding?: string;
132 |
133 | /**
134 | * Number of lines of context to include before each match.
135 | */
136 | beforeContext?: number;
137 |
138 | /**
139 | * Number of lines of context to include after each match.
140 | */
141 | afterContext?: number;
142 | }
143 |
144 | /**
145 | * Represents the severity of a TextSearchComplete message.
146 | */
147 | export enum TextSearchCompleteMessageType {
148 | Information = 1,
149 | Warning = 2,
150 | }
151 |
152 | /**
153 | * A message regarding a completed search.
154 | */
155 | export interface TextSearchCompleteMessage {
156 | /**
157 | * Markdown text of the message.
158 | */
159 | text: string;
160 | /**
161 | * Whether the source of the message is trusted, command links are disabled for untrusted message sources.
162 | * Messaged are untrusted by default.
163 | */
164 | trusted?: boolean;
165 | /**
166 | * The message type, this affects how the message will be rendered.
167 | */
168 | type: TextSearchCompleteMessageType;
169 | }
170 |
171 | /**
172 | * Information collected when text search is complete.
173 | */
174 | export interface TextSearchComplete {
175 | /**
176 | * Whether the search hit the limit on the maximum number of search results.
177 | * `maxResults` on {@linkcode TextSearchOptions} specifies the max number of results.
178 | * - If exactly that number of matches exist, this should be false.
179 | * - If `maxResults` matches are returned and more exist, this should be true.
180 | * - If search hits an internal limit which is less than `maxResults`, this should be true.
181 | */
182 | limitHit?: boolean;
183 |
184 | /**
185 | * Additional information regarding the state of the completed search.
186 | *
187 | * Messages with "Information" style support links in markdown syntax:
188 | * - Click to [run a command](command:workbench.action.OpenQuickPick)
189 | * - Click to [open a website](https://aka.ms)
190 | *
191 | * Commands may optionally return { triggerSearch: true } to signal to the editor that the original search should run be again.
192 | */
193 | message?: TextSearchCompleteMessage | TextSearchCompleteMessage[];
194 | }
195 |
196 | /**
197 | * A preview of the text result.
198 | */
199 | export interface TextSearchMatchPreview {
200 | /**
201 | * The matching lines of text, or a portion of the matching line that contains the match.
202 | */
203 | text: string;
204 |
205 | /**
206 | * The Range within `text` corresponding to the text of the match.
207 | * The number of matches must match the TextSearchMatch's range property.
208 | */
209 | matches: Range | Range[];
210 | }
211 |
212 | /**
213 | * A match from a text search
214 | */
215 | export interface TextSearchMatch {
216 | /**
217 | * The uri for the matching document.
218 | */
219 | uri: Uri;
220 |
221 | /**
222 | * The range of the match within the document, or multiple ranges for multiple matches.
223 | */
224 | ranges: Range | Range[];
225 |
226 | /**
227 | * A preview of the text match.
228 | */
229 | preview: TextSearchMatchPreview;
230 | }
231 |
232 | /**
233 | * A line of context surrounding a TextSearchMatch.
234 | */
235 | export interface TextSearchContext {
236 | /**
237 | * The uri for the matching document.
238 | */
239 | uri: Uri;
240 |
241 | /**
242 | * One line of text.
243 | * previewOptions.charsPerLine applies to this
244 | */
245 | text: string;
246 |
247 | /**
248 | * The line number of this line of context.
249 | */
250 | lineNumber: number;
251 | }
252 |
253 | export type TextSearchResult = TextSearchMatch | TextSearchContext;
254 |
255 | /**
256 | * A TextSearchProvider provides search results for text results inside files in the workspace.
257 | */
258 | export interface TextSearchProvider {
259 | /**
260 | * Provide results that match the given text pattern.
261 | * @param query The parameters for this query.
262 | * @param options A set of options to consider while searching.
263 | * @param progress A progress callback that must be invoked for all results.
264 | * @param token A cancellation token.
265 | */
266 | provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): ProviderResult;
267 | }
268 |
269 | export namespace workspace {
270 | /**
271 | * Register a text search provider.
272 | *
273 | * Only one provider can be registered per scheme.
274 | *
275 | * @param scheme The provider will be invoked for workspace folders that have this file scheme.
276 | * @param provider The provider.
277 | * @return A {@link Disposable} that unregisters this provider when being disposed.
278 | */
279 | export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable;
280 | }
281 | }
282 |
--------------------------------------------------------------------------------