Compare commits

..

10 Commits

Author SHA1 Message Date
Karol Krzosa
ed33df2c16 Fix comma 2026-02-16 17:49:58 +01:00
Karol Krzosa
c4e6c7cd83 Trim test 2026-02-16 17:49:38 +01:00
Karol Krzosa
3087896926 Fix README not found 2026-02-16 17:43:23 +01:00
Karol Krzosa
2f3b15cd69 Add welcome page showing README.md on first install 2026-02-16 17:34:34 +01:00
Karol Krzosa
c6851c8951 Use microsoft extension as reference and fix Workspace search 2026-02-16 17:29:52 +01:00
Karol Krzosa
cd70bcd23e Checkpoint 2026-02-16 15:51:37 +01:00
Karol Krzosa
e0687c649b Remove unneeded 2026-02-16 15:07:59 +01:00
Karol Krzosa
6a8679649d Use native VS Code APIs: openTextDocument for file resolution, workspace.fs.stat, Uri.joinPath
- Replace manual fs.readFile + LRU cache with vscode.workspace.openTextDocument
  (reuses VS Code's document buffers, picks up unsaved changes)
- Replace fs.stat with vscode.workspace.fs.stat
- Replace vscode.Uri.file(path.join(...)) with vscode.Uri.joinPath
- Store wsRootUri alongside wsRoot for native Uri operations
- Remove fs/promises import (only createReadStream remains for streaming parser)
2026-02-16 15:04:43 +01:00
Karol Krzosa
dd1e0aed90 Debounce, prevent from reloading while tags is being generated 2026-02-16 14:23:57 +01:00
Karol Krzosa
07cc5a2435 Add progress during parsing 2026-02-16 14:23:32 +01:00
5 changed files with 367 additions and 180 deletions

View File

@@ -1,9 +0,0 @@
# Change Log
All notable changes to the "vsctags" extension will be documented in this file.
Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file.
## [Unreleased]
- Initial release

View File

@@ -23,6 +23,10 @@
{
"command": "vsctags.showLog",
"title": "vsctags: Show Log"
},
{
"command": "vsctags.showWelcome",
"title": "vsctags: Welcome"
}
]
},
@@ -31,8 +35,7 @@
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"pretest": "npm run compile && npm run lint",
"lint": "eslint src",
"test": "vscode-test"
"lint": "eslint src"
},
"devDependencies": {
"@types/vscode": "^1.109.0",

View File

@@ -1,6 +1,5 @@
import * as vscode from "vscode";
import * as path from "path";
import * as fs from "fs/promises";
import { createReadStream } from "fs";
import * as readline from "readline";
@@ -50,9 +49,14 @@ function extractSearchText(pattern: string): string {
* Parse a ctags file using streaming readline.
* Avoids loading the entire file into memory as a single string.
*/
function streamParseCtagsFile(tagsPath: string): Promise<CtagsEntry[]> {
function streamParseCtagsFile(
tagsPath: string,
onProgress?: (count: number) => void,
): Promise<CtagsEntry[]> {
return new Promise((resolve, reject) => {
const entries: CtagsEntry[] = [];
let progressCounter = 0;
const progressInterval = 50000; // report every 50K entries
const rl = readline.createInterface({
input: createReadStream(tagsPath, { encoding: "utf-8" }),
crlfDelay: Infinity,
@@ -118,9 +122,17 @@ function streamParseCtagsFile(tagsPath: string): Promise<CtagsEntry[]> {
lineNumber,
scope,
});
progressCounter++;
if (onProgress && progressCounter % progressInterval === 0) {
onProgress(progressCounter);
}
});
rl.on("close", () => resolve(entries));
rl.on("close", () => {
if (onProgress) { onProgress(entries.length); }
resolve(entries);
});
rl.on("error", (err: Error) => reject(err));
});
}
@@ -224,21 +236,31 @@ function substringSearch(db: TagDatabase, query: string, limit: number): CtagsEn
return results;
}
// ---- URI cache ----
/** Cache of file URIs to avoid redundant Uri.joinPath calls */
let fileUriCache = new Map<string, vscode.Uri>();
function getFileUri(relPath: string): vscode.Uri {
let uri = fileUriCache.get(relPath);
if (!uri) {
uri = vscode.Uri.joinPath(wsRootUri, relPath);
fileUriCache.set(relPath, uri);
}
return uri;
}
// ---- Lazy line number resolution ----
/** Cache of file contents for lazy resolution, keyed by absolute path */
const fileContentCache = new Map<string, string[] | null>();
async function getFileLines(absPath: string): Promise<string[] | null> {
const cached = fileContentCache.get(absPath);
if (cached !== undefined) { return cached; }
/**
* Get file contents via VS Code's document model.
* Uses already-loaded buffers when available, picks up unsaved changes,
* and lets VS Code manage its own caching — no duplicate memory usage.
*/
async function getDocument(uri: vscode.Uri): Promise<vscode.TextDocument | null> {
try {
const content = await fs.readFile(absPath, "utf-8");
const lines = content.split("\n");
fileContentCache.set(absPath, lines);
return lines;
return await vscode.workspace.openTextDocument(uri);
} catch {
fileContentCache.set(absPath, null);
return null;
}
}
@@ -247,7 +269,7 @@ async function getFileLines(absPath: string): Promise<string[] | null> {
* Resolve the line number of an entry lazily. If already resolved, returns immediately.
* Otherwise reads the source file (with caching) and finds the pattern.
*/
async function resolveLineNumber(entry: CtagsEntry, root: string): Promise<number> {
async function resolveLineNumber(entry: CtagsEntry, rootUri: vscode.Uri): Promise<number> {
if (entry.lineNumber >= 0) { return entry.lineNumber; }
const searchText = extractSearchText(entry.pattern);
@@ -256,15 +278,14 @@ async function resolveLineNumber(entry: CtagsEntry, root: string): Promise<numbe
return 0;
}
const absPath = path.join(root, entry.file);
const lines = await getFileLines(absPath);
if (!lines) {
const doc = await getDocument(getFileUri(entry.file));
if (!doc) {
entry.lineNumber = 0;
return 0;
}
for (let i = 0; i < lines.length; i++) {
if (lines[i].includes(searchText)) {
for (let i = 0; i < doc.lineCount; i++) {
if (doc.lineAt(i).text.includes(searchText)) {
entry.lineNumber = i;
return i;
}
@@ -275,36 +296,18 @@ async function resolveLineNumber(entry: CtagsEntry, root: string): Promise<numbe
/**
* Resolve line numbers for a batch of entries.
* Groups by file to avoid redundant reads.
*/
async function resolveEntries(entries: CtagsEntry[], root: string): Promise<void> {
// Collect unique files that need resolution
const filesToResolve = new Set<string>();
async function resolveEntries(entries: CtagsEntry[], rootUri: vscode.Uri): Promise<void> {
for (const e of entries) {
if (e.lineNumber === -1) {
filesToResolve.add(e.file);
}
}
// Pre-load files in parallel (up to 20 at a time)
const files = Array.from(filesToResolve);
const batchSize = 20;
for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize);
await Promise.all(batch.map(f => getFileLines(path.join(root, f))));
}
// Now resolve all entries (file contents are cached)
for (const e of entries) {
if (e.lineNumber === -1) {
await resolveLineNumber(e, root);
await resolveLineNumber(e, rootUri);
}
}
}
function entryToLocation(entry: CtagsEntry, root: string): vscode.Location {
function entryToLocation(entry: CtagsEntry): vscode.Location {
const ln = entry.lineNumber >= 0 ? entry.lineNumber : 0;
const uri = vscode.Uri.file(path.join(root, entry.file));
const pos = new vscode.Position(ln, 0);
return new vscode.Location(uri, pos);
return new vscode.Location(getFileUri(entry.file), new vscode.Position(ln, 0));
}
function entryToSymbolKind(kind: string): vscode.SymbolKind {
@@ -358,6 +361,7 @@ function formatDuration(ms: number): string {
let db: TagDatabase = { entries: [], nameIndex: new Map(), fileIndex: new Map(), sorted: [] };
let wsRoot = "";
let wsRootUri: vscode.Uri;
let statusBarItem: vscode.StatusBarItem;
let isLoading = false;
@@ -376,7 +380,7 @@ function updateStatusBar() {
statusBarItem.show();
}
async function loadTags(): Promise<boolean> {
async function loadTags(progress?: vscode.Progress<{ message?: string; increment?: number }>): Promise<boolean> {
if (!wsRoot) { return false; }
if (isLoading) {
logInfo("Load already in progress, skipping.");
@@ -391,17 +395,23 @@ async function loadTags(): Promise<boolean> {
try {
// Stage 1: Stream-parse the file
logInfo("Stage 1/3: Stream-parsing tags file...");
logInfo("Stage 1/2: Stream-parsing tags file...");
const t1 = performance.now();
let fileSizeKB = "?";
let fileSizeBytes = 0;
try {
const stat = await fs.stat(tagsPath);
fileSizeKB = (stat.size / 1024).toFixed(1);
const stat = await vscode.workspace.fs.stat(vscode.Uri.file(tagsPath));
fileSizeBytes = stat.size;
fileSizeKB = (fileSizeBytes / 1024).toFixed(1);
logInfo(` File size: ${fileSizeKB} KB`);
} catch { /* stat failed, continue anyway */ }
const entries = await streamParseCtagsFile(tagsPath);
const entries = await streamParseCtagsFile(tagsPath, (count) => {
const msg = `Parsing tags... ${(count / 1000).toFixed(0)}K entries`;
statusBarItem.text = `$(sync~spin) vsctags: ${(count / 1000).toFixed(0)}K`;
if (progress) { progress.report({ message: msg }); }
});
const parseTime = performance.now() - t1;
const withLineNum = entries.filter(e => e.lineNumber >= 0).length;
const needsResolve = entries.length - withLineNum;
@@ -409,7 +419,9 @@ async function loadTags(): Promise<boolean> {
logInfo(` ${withLineNum} with line numbers, ${needsResolve} need lazy resolution`);
// Stage 2: Build indexes (name, file, sorted)
logInfo("Stage 2/3: Building indexes...");
logInfo("Stage 2/2: Building indexes...");
if (progress) { progress.report({ message: `Building index for ${entries.length} tags...` }); }
statusBarItem.text = `$(sync~spin) vsctags: indexing...`;
const t2 = performance.now();
const newDb = buildDatabase(entries);
const indexTime = performance.now() - t2;
@@ -418,11 +430,8 @@ async function loadTags(): Promise<boolean> {
logInfo(` Sorted array: ${newDb.sorted.length} entries`);
logInfo(` Index build took ${formatDuration(indexTime)}`);
// Stage 3: Clear file content cache (stale data from previous load)
logInfo("Stage 3/3: Clearing resolution cache...");
fileContentCache.clear();
// Commit
// Commit — clear caches before swapping
fileUriCache.clear();
db = newDb;
// Summary
@@ -475,9 +484,9 @@ class CtagsDefinitionProvider implements vscode.DefinitionProvider {
const entries = db.nameIndex.get(word);
if (!entries || entries.length === 0) { return undefined; }
// Lazy-resolve line numbers before navigating
await resolveEntries(entries, wsRoot);
await resolveEntries(entries, wsRootUri);
logInfo(`Definition lookup: "${word}" -> ${entries.length} result(s)`);
return entries.map((e) => entryToLocation(e, wsRoot));
return entries.map((e) => entryToLocation(e));
}
}
@@ -490,7 +499,7 @@ class CtagsHoverProvider implements vscode.HoverProvider {
if (!word) { return undefined; }
const entries = db.nameIndex.get(word);
if (!entries || entries.length === 0) { return undefined; }
await resolveEntries(entries, wsRoot);
await resolveEntries(entries, wsRootUri);
const lines: string[] = [];
for (const entry of entries) {
@@ -505,44 +514,72 @@ class CtagsHoverProvider implements vscode.HoverProvider {
}
class CtagsWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
provideWorkspaceSymbols(query: string): vscode.SymbolInformation[] {
public async provideWorkspaceSymbols(query: string, token: vscode.CancellationToken): Promise<vscode.SymbolInformation[]> {
const t = performance.now();
const limit = 500;
const lowerQuery = query.toLowerCase();
const limit = 200;
const lowerQuery = query.toLowerCase().trim();
let results: CtagsEntry[];
if (lowerQuery.length === 0) {
// No query: return first N entries
results = db.entries.slice(0, limit);
} else {
// Try prefix search first (O(log n + k))
results = prefixSearch(db, lowerQuery, limit);
// If prefix didn't fill up, supplement with substring matches
if (results.length < limit) {
const prefixSet = new Set(results);
const substringResults = substringSearch(db, lowerQuery, limit);
for (const entry of substringResults) {
if (!prefixSet.has(entry)) {
results.push(entry);
if (results.length >= limit) { break; }
}
}
}
if (!lowerQuery) {
return [];
}
const symbols = results.map(entry =>
new vscode.SymbolInformation(
entry.name,
entryToSymbolKind(entry.kind),
entry.scope,
entryToLocation(entry, wsRoot),
),
);
if (token.isCancellationRequested) {
throw new vscode.CancellationError();
}
logInfo(`Workspace symbol search: "${query}" -> ${symbols.length} result(s) in ${formatDuration(performance.now() - t)}`);
return symbols;
try {
// Fast prefix search via binary search — O(log n + k), synchronous
const results = prefixSearch(db, lowerQuery, limit);
if (token.isCancellationRequested) {``
throw new vscode.CancellationError();
}
const symbols = results.map(entry =>
new vscode.SymbolInformation(
entry.name,
entryToSymbolKind(entry.kind),
entry.scope,
entryToLocation(entry),
),
);
if (symbols.length > 0) {
const s = symbols[0];
logInfo(`Workspace symbol search: "${query}" -> ${symbols.length} result(s) in ${formatDuration(performance.now() - t)}, sample: uri=${s.location.uri.toString()}, scheme=${s.location.uri.scheme}, line=${s.location.range.start.line}`);
} else {
logInfo(`Workspace symbol search: "${query}" -> 0 result(s) in ${formatDuration(performance.now() - t)}`);
}
return symbols;
} catch (err) {
if (err instanceof vscode.CancellationError) {
logInfo(`Workspace symbol search cancelled for "${query}" after ${formatDuration(performance.now() - t)}`);
throw err;
}
logError(`Workspace symbol search failed for "${query}": ${err}`);
return [];
}
}
async resolveWorkspaceSymbol(symbol: vscode.SymbolInformation): Promise<vscode.SymbolInformation | undefined> {
const t = performance.now();
// Resolve the exact line number when the user selects a symbol
const relPath = vscode.workspace.asRelativePath(symbol.location.uri, false);
const entries = db.nameIndex.get(symbol.name);
if (!entries) {
logInfo(`resolveWorkspaceSymbol: "${symbol.name}" no entries, ${formatDuration(performance.now() - t)}`);
return symbol;
}
const match = entries.find(e => e.file === relPath);
if (match) {
if (match.lineNumber === -1) {
await resolveLineNumber(match, wsRootUri);
}
symbol.location = entryToLocation(match);
}
logInfo(`resolveWorkspaceSymbol: "${symbol.name}" in ${formatDuration(performance.now() - t)}`);
return symbol;
}
}
@@ -565,7 +602,7 @@ class CtagsDocumentSymbolProvider implements vscode.DocumentSymbolProvider {
entry.name,
entryToSymbolKind(entry.kind),
entry.scope,
entryToLocation(entry, wsRoot),
entryToLocation(entry),
),
);
@@ -583,9 +620,9 @@ class CtagsReferenceProvider implements vscode.ReferenceProvider {
if (!word) { return undefined; }
const entries = db.nameIndex.get(word);
if (!entries || entries.length === 0) { return undefined; }
await resolveEntries(entries, wsRoot);
await resolveEntries(entries, wsRootUri);
logInfo(`References lookup: "${word}" -> ${entries.length} result(s)`);
return entries.map((e) => entryToLocation(e, wsRoot));
return entries.map((e) => entryToLocation(e));
}
}
@@ -602,7 +639,8 @@ export function activate(context: vscode.ExtensionContext) {
logInfo("No workspace folder open. Extension idle.");
return;
}
wsRoot = folders[0].uri.fsPath;
wsRootUri = folders[0].uri;
wsRoot = wsRootUri.fsPath;
logInfo(`Workspace root: ${wsRoot}`);
// Status bar
@@ -612,10 +650,10 @@ export function activate(context: vscode.ExtensionContext) {
// Initial load with progress
vscode.window.withProgress(
{ location: vscode.ProgressLocation.Window, title: "vsctags: Loading tags..." },
{ location: vscode.ProgressLocation.Window, title: "vsctags" },
async (progress) => {
progress.report({ message: "Reading tags file..." });
const ok = await loadTags();
progress.report({ message: "Loading tags..." });
const ok = await loadTags(progress);
if (ok) {
progress.report({ message: `Loaded ${db.entries.length} tags` });
}
@@ -634,24 +672,31 @@ export function activate(context: vscode.ExtensionContext) {
);
logInfo("Language providers registered (definition, hover, workspace symbols, document symbols, references)");
// Watch the tags file for changes
// Watch the tags file for changes (debounced)
const tagsPattern = new vscode.RelativePattern(folders[0], "tags");
const watcher = vscode.workspace.createFileSystemWatcher(tagsPattern);
const debounceMs = 2000;
let debounceTimer: ReturnType<typeof setTimeout> | undefined;
watcher.onDidChange(async () => {
logInfo("Tags file changed on disk. Reloading...");
await loadTags();
vscode.window.showInformationMessage(`[vsctags] Reloaded ${db.entries.length} tags.`);
});
watcher.onDidCreate(async () => {
logInfo("Tags file created. Loading...");
await loadTags();
vscode.window.showInformationMessage(`[vsctags] Loaded ${db.entries.length} tags.`);
});
function debouncedReload(reason: string) {
if (debounceTimer) { clearTimeout(debounceTimer); }
logInfo(`Tags file ${reason}. Waiting ${debounceMs}ms for stability...`);
statusBarItem.text = "$(sync~spin) vsctags: file changed...";
debounceTimer = setTimeout(async () => {
debounceTimer = undefined;
logInfo("Debounce elapsed, reloading tags.");
await loadTags();
vscode.window.showInformationMessage(`[vsctags] Reloaded ${db.entries.length} tags.`);
}, debounceMs);
}
watcher.onDidChange(() => debouncedReload("changed"));
watcher.onDidCreate(() => debouncedReload("created"));
watcher.onDidDelete(() => {
if (debounceTimer) { clearTimeout(debounceTimer); debounceTimer = undefined; }
logInfo("Tags file deleted.");
db = { entries: [], nameIndex: new Map(), fileIndex: new Map(), sorted: [] };
fileContentCache.clear();
fileUriCache.clear();
updateStatusBar();
vscode.window.showInformationMessage("[vsctags] Tags file removed.");
});
@@ -665,7 +710,7 @@ export function activate(context: vscode.ExtensionContext) {
logInfo("Manual reload requested.");
const ok = await vscode.window.withProgress(
{ location: vscode.ProgressLocation.Notification, title: "vsctags: Reloading tags..." },
async () => loadTags(),
async (progress) => loadTags(progress),
);
if (ok) {
vscode.window.showInformationMessage(`[vsctags] Reloaded ${db.entries.length} tags.`);
@@ -682,10 +727,217 @@ export function activate(context: vscode.ExtensionContext) {
}),
);
// Welcome page command
context.subscriptions.push(
vscode.commands.registerCommand("vsctags.showWelcome", () => {
showWelcomePage(context);
}),
);
// Auto-show welcome page on first install
const hasShownWelcome = context.globalState.get<boolean>("vsctags.welcomeShown");
if (!hasShownWelcome) {
showWelcomePage(context);
context.globalState.update("vsctags.welcomeShown", true);
}
logInfo("Extension activated.");
}
// ---- Welcome Page ----
async function showWelcomePage(context: vscode.ExtensionContext) {
const panel = vscode.window.createWebviewPanel(
"vsctags.welcome",
"vsctags — Welcome",
vscode.ViewColumn.One,
{ enableScripts: false },
);
let markdown = "";
try {
// Try both cases — vsce may lowercase the filename in the VSIX
let bytes: Uint8Array | undefined;
for (const name of ["README.md", "readme.md"]) {
try {
bytes = await vscode.workspace.fs.readFile(vscode.Uri.joinPath(context.extensionUri, name));
break;
} catch { /* try next */ }
}
if (bytes) {
markdown = Buffer.from(bytes).toString("utf-8");
} else {
logError(`README not found at extensionUri: ${context.extensionUri.toString()}`);
markdown = "# vsctags\n\nREADME.md not found.";
}
} catch {
markdown = "# vsctags\n\nREADME.md not found.";
}
panel.webview.html = renderMarkdownHtml(markdown);
}
/** Minimal Markdown to HTML renderer — no dependencies */
function renderMarkdownHtml(md: string): string {
let html = escapeHtml(md);
// Code blocks (``` ... ```)
html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, lang, code) => {
return `<pre><code class="language-${lang}">${code.trim()}</code></pre>`;
});
// Inline code
html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
// Tables
html = html.replace(/((?:^\|.+\|\s*$\n?)+)/gm, (_m, table: string) => {
const rows = table.trim().split("\n").filter(r => r.trim().length > 0);
if (rows.length < 2) { return table; }
// Check if second row is separator (|---|---|)
const isSep = /^\|[\s\-:|]+\|$/.test(rows[1].trim());
if (!isSep) { return table; }
const parseRow = (row: string) =>
row.split("|").slice(1, -1).map(c => c.trim());
const headerCells = parseRow(rows[0]);
const thead = "<tr>" + headerCells.map(c => `<th>${c}</th>`).join("") + "</tr>";
const bodyRows = rows.slice(2).map(row => {
const cells = parseRow(row);
return "<tr>" + cells.map(c => `<td>${c}</td>`).join("") + "</tr>";
}).join("\n");
return `<table><thead>${thead}</thead><tbody>${bodyRows}</tbody></table>`;
});
// Headers
html = html.replace(/^######\s+(.+)$/gm, "<h6>$1</h6>");
html = html.replace(/^#####\s+(.+)$/gm, "<h5>$1</h5>");
html = html.replace(/^####\s+(.+)$/gm, "<h4>$1</h4>");
html = html.replace(/^###\s+(.+)$/gm, "<h3>$1</h3>");
html = html.replace(/^##\s+(.+)$/gm, "<h2>$1</h2>");
html = html.replace(/^#\s+(.+)$/gm, "<h1>$1</h1>");
// Bold + italic
html = html.replace(/\*\*\*(.+?)\*\*\*/g, "<strong><em>$1</em></strong>");
html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
html = html.replace(/\*(.+?)\*/g, "<em>$1</em>");
// Links
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
// Unordered lists
html = html.replace(/((?:^- .+$\n?)+)/gm, (_m, block: string) => {
const items = block.trim().split("\n").map(l => `<li>${l.replace(/^- /, "")}</li>`);
return `<ul>${items.join("\n")}</ul>`;
});
// Ordered lists
html = html.replace(/((?:^\d+\.\s.+$\n?)+)/gm, (_m, block: string) => {
const items = block.trim().split("\n").map(l => `<li>${l.replace(/^\d+\.\s/, "")}</li>`);
return `<ol>${items.join("\n")}</ol>`;
});
// Horizontal rules
html = html.replace(/^---$/gm, "<hr>");
// Paragraphs — wrap remaining loose lines
html = html.replace(/^(?!<[a-z])(\S.+)$/gm, "<p>$1</p>");
// Clean up empty lines
html = html.replace(/\n{3,}/g, "\n\n");
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: var(--vscode-font-family, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
font-size: var(--vscode-font-size, 14px);
color: var(--vscode-foreground);
background: var(--vscode-editor-background);
max-width: 800px;
margin: 0 auto;
padding: 24px;
line-height: 1.6;
}
h1, h2, h3, h4, h5, h6 {
color: var(--vscode-foreground);
border-bottom: 1px solid var(--vscode-panel-border, #333);
padding-bottom: 4px;
margin-top: 24px;
}
h1 { font-size: 2em; }
h2 { font-size: 1.5em; }
h3 { font-size: 1.25em; }
code {
font-family: var(--vscode-editor-font-family, 'Menlo', 'Consolas', monospace);
background: var(--vscode-textCodeBlock-background, rgba(128,128,128,0.15));
padding: 2px 5px;
border-radius: 3px;
font-size: 0.9em;
}
pre {
background: var(--vscode-textCodeBlock-background, rgba(128,128,128,0.15));
padding: 12px 16px;
border-radius: 4px;
overflow-x: auto;
}
pre code {
background: none;
padding: 0;
}
table {
border-collapse: collapse;
width: 100%;
margin: 16px 0;
}
th, td {
border: 1px solid var(--vscode-panel-border, #444);
padding: 8px 12px;
text-align: left;
}
th {
background: var(--vscode-textCodeBlock-background, rgba(128,128,128,0.15));
font-weight: 600;
}
a {
color: var(--vscode-textLink-foreground, #3794ff);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
ul, ol {
padding-left: 24px;
}
li {
margin: 4px 0;
}
hr {
border: none;
border-top: 1px solid var(--vscode-panel-border, #333);
margin: 24px 0;
}
</style>
</head>
<body>
${html}
</body>
</html>`;
}
function escapeHtml(text: string): string {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
export function deactivate() {
logInfo("Extension deactivated.");
fileContentCache.clear();
}

View File

@@ -1,15 +0,0 @@
import * as assert from 'assert';
// You can import and use all API from the 'vscode' module
// as well as import your extension to test it
import * as vscode from 'vscode';
// import * as myExtension from '../../extension';
suite('Extension Test Suite', () => {
vscode.window.showInformationMessage('Start all tests.');
test('Sample test', () => {
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
});
});

View File

@@ -1,44 +0,0 @@
# Welcome to your VS Code Extension
## What's in the folder
* This folder contains all of the files necessary for your extension.
* `package.json` - this is the manifest file in which you declare your extension and command.
* 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 doesnt yet need to load the plugin.
* `src/extension.ts` - this is the main file where you will provide the implementation of your command.
* 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`.
* We pass the function containing the implementation of the command as the second parameter to `registerCommand`.
## Get up and running straight away
* Press `F5` to open a new window with your extension loaded.
* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`.
* Set breakpoints in your code inside `src/extension.ts` to debug your extension.
* Find output from your extension in the debug console.
## Make changes
* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`.
* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes.
## Explore the API
* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`.
## Run tests
* Install the [Extension Test Runner](https://marketplace.visualstudio.com/items?itemName=ms-vscode.extension-test-runner)
* Run the "watch" task via the **Tasks: Run Task** command. Make sure this is running, or tests might not be discovered.
* Open the Testing view from the activity bar and click the Run Test" button, or use the hotkey `Ctrl/Cmd + ; A`
* See the output of the test result in the Test Results view.
* Make changes to `src/test/extension.test.ts` or create new test files inside the `test` folder.
* The provided test runner will only consider files matching the name pattern `**.test.ts`.
* You can create folders inside the `test` folder to structure your tests any way you want.
## Go further
* [Follow UX guidelines](https://code.visualstudio.com/api/ux-guidelines/overview) to create extensions that seamlessly integrate with VS Code's native interface and patterns.
* Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension).
* [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VS Code extension marketplace.
* Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration).
* Integrate to the [report issue](https://code.visualstudio.com/api/get-started/wrapping-up#issue-reporting) flow to get issue and feature requests reported by users.