Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 33 additions & 56 deletions src/Client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,38 @@
Edit, run and chart Kusto queries (KQL).
Explore databases and query results.

## Features

- KQL Documents
- IntelliSense (auto completions)
- Syntax and Semantic coloring
- Semantic Copy/Paste
- Copy query text as colorized html
- Hover Tips
- Describes item under the mouse cursor
- Shows related diagnostics
- Syntax highlighting
- Highlights references to the same item
- Highlights balanced parentheses, brackets and braces
- Goto Definition
- Goto the declaration location of items declared in your query or as part of the database
- Find All References
- Find all references to an item in your query
- Diagnostics (errors/warnings)
- All kusto diagnostics appear in VS Code Problems tab
- Formatting
- Pretty print a single query using Format button above first line of query
- Pretty print the entire document using VS Code's Format Document (ALT-SHIFT-F).
- Adjust format settings in VS Code `Settings/Extensions/Kusto`
- Query execution
- Place the caret inside the query and press F5
- Code Actions
- Quick fixes are indicated by ellipses beneath the related item, and are accissible within the hover tip.
- Refactorings are indicated by a lightbulb menu that appears after clicking on related text item.
- CoPilot also makes suggestions and offers fixes in the same UI.
- Connection panel
- Add and remove server connections
- Add and remove server group folders
- Drag and drop server into and out of groups folders
- Assign a connection to a KQL document by selecting a database
- Explore database contents and view entity definitions
- Results Panel
- View and explorer the tabular results of queries
- Tab through multiple result tables
- Copy a table as html and markdown
- Copy a table as a Kusto datatable expression
- Chart Panel
- View chart as result of query with render operator
- Copy charts as SVG, PNG and bitmap
- Chat Panel
- Ask CoPilot questions using @kusto in the chat interface

## Usage

1. Open a `.kql` or `.csl` file
2. Select or add a connection in the connections view of the explorer panel
3. Select a default database for the the document under the connection item
4. Write your query
5. Press F5 to execute
## Get Started
- Open or create a `.kql` file in VS Code
- Adjust placement of connection panel (it will appear at bottom of list)
- Add a server connection and select a default database for the document
- Write a Kusto query or consult copilot for help writing one
- Press F5 to execute the query and view results in the results panel
- Use the chart button in the results panel to create a chart for your result data
- Edit the chart options to customize the chart type, axes, legend and more
- Add another query to the kql document, rinse and repeat
- Save your results and charts as a .kqr file to share with others or re-open later

## Query Editor (.kql documents)
- Syntax and semantic coloring of query text
- Autocompletion (Intellisense)
- Hover tips
- Pretty Printing (formatting)
- Goto definition and find all references for functions, tables, columns, and more
- Code actions and quick fixes for common issues and refactorings
- Copy colorized query text to the clipboard for pasting into other documents
- Have mutliple independent queries in the same document, separated by a blank line.

## Results Panel (bottom panel)
- Copy contents of cells or entire table to clipboard
- Drag and drop a table into your document as a KQL datatable expression
- Chart your data (it will open in a results viewer tab)

## Results Viewer (.kqr documents)
- Shows both result data and chart in the same view
- Copy the chart as an image to clipboard for either light-mode or dark-mode pasting.
- Copy the data to clipboard just like results panel.
- Edit the chart options to customize the chart type, axes, legend and more
- Save to a .kqr file to share with others or re-open later

## Requirements

- VS Code 1.9.0 or higher

- VS Code 1.9.0 or higher
20 changes: 8 additions & 12 deletions src/Client/features/documentPanels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,24 +304,20 @@ async function copyQueryTransparent(client: LanguageClient, codeLensRange?: serv
return;
}

const selection = {
start: { line: queryRange.start.line, character: queryRange.start.character },
end: { line: queryRange.end.line, character: queryRange.end.character }
};

// Request light-mode HTML from the server (darkMode = false)
const result = await server.getQueryAsHtml(client, uri, selection, false);
if (!result?.html) {
return;
}

// Get plain text of the query for the text format
// Get plain text of the query
const range = new vscode.Range(
queryRange.start.line, queryRange.start.character,
queryRange.end.line, queryRange.end.character
);
const plainText = editor.document.getText(range);

// Request light-mode HTML from the server (darkMode = false)
const connection = await getDocumentConnection(uri);
const result = await server.getQueryAsHtml(client, plainText, connection?.cluster, connection?.database, false);
if (!result?.html) {
return;
}

// Place both HTML and plain text on the clipboard
const items: import('./clipboard').ClipboardItem[] = [
{ format: 'HTML Format', data: wrapHtmlForClipboard(result.html) },
Expand Down
101 changes: 78 additions & 23 deletions src/Client/features/resultsViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
const state = editorStates.get(webviewPanel);
const hasChart = !!state?.resultData?.chartOptions;
vscode.commands.executeCommand('setContext', 'kusto.resultEditorHasChart', hasChart);
vscode.commands.executeCommand('setContext', 'kusto.resultEditorChartActive', state?.activeView === 'chart');
}
};
webviewPanel.onDidChangeViewState(() => updateChartContext());
Expand All @@ -417,6 +418,7 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
if (state) {
state.activeView = message.viewId;
}
vscode.commands.executeCommand('setContext', 'kusto.resultEditorChartActive', message.viewId === 'chart');
return;
}
if (message.command === 'copyText' && typeof message.text === 'string') {
Expand All @@ -429,19 +431,22 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
state.chartOptionsOverride = message.chartOptions as server.ChartOptions;
if (chartOptionsTimer) { clearTimeout(chartOptionsTimer); }
chartOptionsTimer = setTimeout(async () => {
await this.updateChartOnly(state, webviewPanel);
// Persist updated chart options to the backing file
const updatedData: server.ResultData = { ...state.resultData, chartOptions: state.chartOptionsOverride! };
const content = JSON.stringify(updatedData, null, 2);
const fullRange = new vscode.Range(
document.positionAt(0),
document.positionAt(document.getText().length)
);
const edit = new vscode.WorkspaceEdit();
edit.replace(document.uri, fullRange, content);
ignoringSelfEdit = true;
await vscode.workspace.applyEdit(edit);
ignoringSelfEdit = false;
try {
await this.updateChartOnly(state, webviewPanel);
// Persist updated chart options to the backing file
const updatedData: server.ResultData = { ...state.resultData, chartOptions: state.chartOptionsOverride! };
const content = JSON.stringify(updatedData, null, 2);
const fullRange = new vscode.Range(
document.positionAt(0),
document.positionAt(document.getText().length)
);
const edit = new vscode.WorkspaceEdit();
edit.replace(document.uri, fullRange, content);
ignoringSelfEdit = true;
await vscode.workspace.applyEdit(edit);
} finally {
ignoringSelfEdit = false;
}
}, 600);
return;
}
Expand Down Expand Up @@ -488,6 +493,7 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
if (chartOptionsTimer) { clearTimeout(chartOptionsTimer); }
editorStates.delete(webviewPanel);
vscode.commands.executeCommand('setContext', 'kusto.resultEditorHasChart', false);
vscode.commands.executeCommand('setContext', 'kusto.resultEditorChartActive', false);
changeSubscription.dispose();
themeSubscription.dispose();
});
Expand All @@ -511,7 +517,7 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {

const darkMode = isDarkMode();

// Fetch both table HTML and chart HTML in parallel
// Fetch table HTML and chart HTML in parallel
const [dataResult, chartResult] = await Promise.all([
Promise.resolve(resultDataToHtml(resultData)),
resultData.chartOptions
Expand All @@ -525,6 +531,8 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
// Update context key for editor title actions
if (webviewPanel.active) {
vscode.commands.executeCommand('setContext', 'kusto.resultEditorHasChart', hasChart);
const activeView = editorStates.get(webviewPanel)?.activeView ?? (hasChart ? 'chart' : 'table-0');
vscode.commands.executeCommand('setContext', 'kusto.resultEditorChartActive', activeView === 'chart');
}

if (!hasTable && !hasChart) {
Expand All @@ -539,12 +547,14 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
editorStates.set(webviewPanel, {
resultData,
tableNames,
activeView: existingState?.activeView ?? firstActiveView
activeView: existingState?.activeView ?? firstActiveView,
chartOptionsOverride: existingState?.chartOptionsOverride
});

const chartOptions = existingState?.chartOptionsOverride ?? resultData.chartOptions;
const columnNames = resultData.tables[0]?.columns?.map(c => c.name) ?? [];
const html = this.buildDualViewHtml(dataResult, chartResult?.html, hasChart, chartOptions, columnNames);
const html = this.buildDualViewHtml(dataResult, chartResult?.html, hasChart, chartOptions, columnNames,
resultData.query, resultData.cluster, resultData.database);
webviewPanel.webview.html = injectChartMessageHandler(html);
}

Expand All @@ -570,13 +580,16 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
chartHtml: string | undefined,
hasChart: boolean,
chartOptions?: server.ChartOptions,
columnNames?: string[]
columnNames?: string[],
queryText?: string,
cluster?: string,
database?: string
): string {
const tables = dataResult?.tables ?? [];

// Build individual table divs
const tableContents = tables.map((t, i) =>
`<div id="table-${i}" class="view-content" data-vscode-context='{"chartVisible": false, "preventDefaultContextMenuItems": true}'>${t.html}</div>`
`<div id="table-${i}" class="view-content" data-vscode-context='{"chartVisible": false, "queryVisible": false, "preventDefaultContextMenuItems": true}'>${t.html}</div>`
).join('');

// Extract the chart body content from the full HTML
Expand All @@ -603,6 +616,11 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
).join('');
}

const hasQuery = !!queryText;
const queryButton = hasQuery
? `<button data-view="query" onclick="switchView('query')">Query</button>`
: '';

const firstActiveView = hasChart ? 'chart' : 'table-0';

// Build chart options edit panel
Expand Down Expand Up @@ -777,6 +795,7 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
opacity: 1;
}
/* Table styles */
.view-content table:first-child { margin-top: 4px; }
table { border-collapse: collapse; width: 100%; }
th, td {
padding: 4px 8px;
Expand All @@ -791,17 +810,46 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
z-index: 1;
font-weight: 600;
}
/* Query tab styles */
.query-info {
padding: 8px 12px;
display: flex;
gap: 16px;
border-bottom: 1px solid var(--vscode-panel-border, #444);
background: var(--vscode-editorWidget-background, var(--vscode-editor-background));
}
.query-meta {
font-size: 12px;
color: var(--vscode-descriptionForeground, var(--vscode-foreground));
}
.query-content {
margin: 0;
padding: 4px 12px;
overflow: auto;
font-family: var(--vscode-editor-font-family, 'Consolas', 'Courier New', monospace);
font-size: var(--vscode-editor-font-size, 13px);
white-space: pre-wrap;
color: var(--vscode-descriptionForeground, grey);
}
</style>
</head>
<body>
<div class="view-toggle">
${chartButton}
${tableButtons}
${queryButton}
</div>
<div class="main-area">
<div class="content-area">
${hasChart ? `<div id="chart" class="view-content${firstActiveView === 'chart' ? ' active' : ''}" data-vscode-context='{"chartVisible": true, "preventDefaultContextMenuItems": true}'>${chartContent}</div>` : ''}
${hasChart ? `<div id="chart" class="view-content${firstActiveView === 'chart' ? ' active' : ''}" data-vscode-context='{"chartVisible": true, "queryVisible": false, "preventDefaultContextMenuItems": true}'>${chartContent}</div>` : ''}
${tableContents}
${hasQuery ? `<div id="query" class="view-content" data-vscode-context='{"chartVisible": false, "queryVisible": true, "preventDefaultContextMenuItems": true}'>
<div class="query-info">
${cluster ? `<span class="query-meta"><strong>Cluster:</strong> ${this.escapeHtml(cluster)}</span>` : ''}
${database ? `<span class="query-meta"><strong>Database:</strong> ${this.escapeHtml(database)}</span>` : ''}
</div>
<pre class="query-content">${this.escapeHtml(queryText!)}</pre>
</div>` : ''}
</div>
${editPanelHtml}
</div>
Expand All @@ -825,7 +873,7 @@ export class ResultEditorProvider implements vscode.CustomTextEditorProvider {
// Hide/restore edit panel based on view and user preference
var editPanel = document.getElementById('edit-panel');
if (editPanel) {
if (viewId.startsWith('table-')) {
if (viewId.startsWith('table-') || viewId === 'query') {
editPanel.classList.remove('visible');
} else if (editPanelUserVisible) {
editPanel.classList.add('visible');
Expand Down Expand Up @@ -1201,7 +1249,8 @@ export async function displayChart(

const hasChart = !!chartResult?.html;
const columnNames = resultData.tables[0]?.columns?.map(c => c.name) ?? [];
const html = htmlBuilder.buildDualViewHtml(dataResult, chartResult?.html, hasChart, chartOptions, columnNames);
const html = htmlBuilder.buildDualViewHtml(dataResult, chartResult?.html, hasChart, chartOptions, columnNames,
resultData.query, resultData.cluster, resultData.database);

showSingletonPanel(injectChartMessageHandler(html), resultData, (dataResult?.tables ?? []).map(t => t.name));
}
Expand Down Expand Up @@ -1230,7 +1279,7 @@ function showSingletonPanel(html: string, resultData: server.ResultData, tableNa
if (!singletonPanel) {
singletonPanel = vscode.window.createWebviewPanel(
'kusto',
'Chart',
'Results',
{ viewColumn, preserveFocus: true },
{ enableScripts: true, retainContextWhenHidden: true }
);
Expand All @@ -1240,15 +1289,18 @@ function showSingletonPanel(html: string, resultData: server.ResultData, tableNa

singletonPanel.onDidChangeViewState(() => {
if (singletonPanel?.active) {
const hasChart = !!editorStates.get(singletonPanel)?.resultData?.chartOptions;
const state = editorStates.get(singletonPanel);
const hasChart = !!state?.resultData?.chartOptions;
vscode.commands.executeCommand('setContext', 'kusto.resultEditorHasChart', hasChart);
vscode.commands.executeCommand('setContext', 'kusto.resultEditorChartActive', state?.activeView === 'chart');
}
});

singletonPanel.webview.onDidReceiveMessage(async (message) => {
if (message.command === 'viewChanged' && typeof message.viewId === 'string') {
const state = editorStates.get(singletonPanel!);
if (state) { state.activeView = message.viewId; }
vscode.commands.executeCommand('setContext', 'kusto.resultEditorChartActive', message.viewId === 'chart');
return;
}
if (message.command === 'copyText' && typeof message.text === 'string') {
Expand Down Expand Up @@ -1279,6 +1331,7 @@ function showSingletonPanel(html: string, resultData: server.ResultData, tableNa
singletonResultData = undefined;
singletonChartOptionsOverride = undefined;
vscode.commands.executeCommand('setContext', 'kusto.resultEditorHasChart', false);
vscode.commands.executeCommand('setContext', 'kusto.resultEditorChartActive', false);
vscode.commands.executeCommand('kusto.chartPanelStateChanged');
});
}
Expand All @@ -1291,6 +1344,8 @@ function showSingletonPanel(html: string, resultData: server.ResultData, tableNa
activeView: hasChart ? 'chart' : 'table-0'
});

vscode.commands.executeCommand('setContext', 'kusto.resultEditorChartActive', hasChart);

singletonPanel.webview.html = html;
singletonPanel.reveal(viewColumn, true);
}
Expand Down
Loading
Loading