diff --git a/src/sql/base/browser/ui/table/media/saveMarkdown.svg b/src/sql/base/browser/ui/table/media/saveMarkdown.svg
new file mode 100644
index 0000000000..88e75dd354
--- /dev/null
+++ b/src/sql/base/browser/ui/table/media/saveMarkdown.svg
@@ -0,0 +1,55 @@
+
+
diff --git a/src/sql/base/browser/ui/table/media/saveMarkdown_inverse.svg b/src/sql/base/browser/ui/table/media/saveMarkdown_inverse.svg
new file mode 100644
index 0000000000..bf6adbdc32
--- /dev/null
+++ b/src/sql/base/browser/ui/table/media/saveMarkdown_inverse.svg
@@ -0,0 +1,55 @@
+
+
diff --git a/src/sql/base/browser/ui/table/media/slickColorTheme.css b/src/sql/base/browser/ui/table/media/slickColorTheme.css
index 974446b2fb..d527387cbe 100644
--- a/src/sql/base/browser/ui/table/media/slickColorTheme.css
+++ b/src/sql/base/browser/ui/table/media/slickColorTheme.css
@@ -63,6 +63,11 @@
background-image: url("saveExcel.svg");
}
+.vs .codicon.saveMarkdown {
+ /* ResultToMarkdown_16x_vscode */
+ background-image: url("saveMarkdown.svg");
+}
+
.vs .codicon.saveXml {
/* ResultToXML_16x_vscode */
background-image: url("saveXml.svg");
@@ -172,6 +177,12 @@
background-image: url("saveExcel_inverse.svg");
}
+.vs-dark .codicon.saveMarkdown,
+.hc-black .codicon.saveMarkdown {
+ /* ResultToMarkdown_16x_vscode_inverse.svg */
+ background-image: url("saveMarkdown_inverse.svg");
+}
+
.vs-dark .codicon.saveXml,
.hc-black .codicon.saveXml {
/* ResultToXml_16x_vscode_inverse.svg */
diff --git a/src/sql/platform/query/common/query.ts b/src/sql/platform/query/common/query.ts
index 5f0a0d0ff7..311898632b 100644
--- a/src/sql/platform/query/common/query.ts
+++ b/src/sql/platform/query/common/query.ts
@@ -15,6 +15,11 @@ export interface IQueryEditorConfiguration {
readonly saveAsExcel: {
readonly includeHeaders: boolean,
},
+ readonly saveAsMarkdown: {
+ readonly encoding: string,
+ readonly includeHeaders: boolean,
+ readonly lineSeparator: string,
+ }
readonly saveAsXml: {
readonly formatted: boolean,
readonly encoding: string
diff --git a/src/sql/workbench/contrib/editData/browser/gridActions.ts b/src/sql/workbench/contrib/editData/browser/gridActions.ts
index 33f3e6fb1f..05acbe4e20 100644
--- a/src/sql/workbench/contrib/editData/browser/gridActions.ts
+++ b/src/sql/workbench/contrib/editData/browser/gridActions.ts
@@ -14,6 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
export const GRID_SAVECSV_ID = 'grid.saveAsCsv';
export const GRID_SAVEJSON_ID = 'grid.saveAsJson';
+export const GRID_SAVEMARKDOWN_ID = 'grid.saveAsMarkdown';
export const GRID_SAVEEXCEL_ID = 'grid.saveAsExcel';
export const GRID_SAVEXML_ID = 'grid.saveAsXml';
export const GRID_COPY_ID = 'grid.copySelection';
@@ -46,6 +47,7 @@ export class GridActionProvider {
const actions: IAction[] = [];
actions.push(this._instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveFormat.CSV, this._dataService));
actions.push(this._instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveFormat.JSON, this._dataService));
+ actions.push(this._instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEMARKDOWN_ID, SaveResultAction.SAVEMARKDOWN_LABEL, SaveFormat.MARKDOWN, this._dataService));
actions.push(this._instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveFormat.EXCEL, this._dataService));
actions.push(this._instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveFormat.XML, this._dataService));
actions.push(this._instantiationService.createInstance(SelectAllGridAction, SelectAllGridAction.ID, SelectAllGridAction.LABEL, this._selectAllCallback));
@@ -63,6 +65,9 @@ class SaveResultAction extends Action {
public static SAVEJSON_ID = GRID_SAVEJSON_ID;
public static SAVEJSON_LABEL = localize('saveAsJson', "Save As JSON");
+ public static SAVEMARKDOWN_ID = GRID_SAVEMARKDOWN_ID;
+ public static SAVEMARKDOWN_LABEL = localize('saveAsMarkdown', "Save As Markdown");
+
public static SAVEEXCEL_ID = GRID_SAVEEXCEL_ID;
public static SAVEEXCEL_LABEL = localize('saveAsExcel', "Save As Excel");
diff --git a/src/sql/workbench/contrib/editData/browser/gridCommands.ts b/src/sql/workbench/contrib/editData/browser/gridCommands.ts
index 0e62e74592..f32f294215 100644
--- a/src/sql/workbench/contrib/editData/browser/gridCommands.ts
+++ b/src/sql/workbench/contrib/editData/browser/gridCommands.ts
@@ -63,6 +63,10 @@ export const saveAsJson = (accessor: ServicesAccessor) => {
runActionOnActiveResultsEditor(accessor, GridContentEvents.SaveAsJSON);
};
+export const saveAsMarkdown = (accessor: ServicesAccessor) => {
+ runActionOnActiveResultsEditor(accessor, GridContentEvents.SaveAsMarkdown);
+};
+
export const saveAsExcel = (accessor: ServicesAccessor) => {
runActionOnActiveResultsEditor(accessor, GridContentEvents.SaveAsExcel);
};
diff --git a/src/sql/workbench/contrib/editData/browser/gridParentComponent.ts b/src/sql/workbench/contrib/editData/browser/gridParentComponent.ts
index 52586a7713..d4a60b77f0 100644
--- a/src/sql/workbench/contrib/editData/browser/gridParentComponent.ts
+++ b/src/sql/workbench/contrib/editData/browser/gridParentComponent.ts
@@ -139,6 +139,9 @@ export abstract class GridParentComponent extends Disposable {
case GridContentEvents.SaveAsJSON:
self.sendSaveRequest(SaveFormat.JSON);
break;
+ case GridContentEvents.SaveAsMarkdown:
+ self.sendSaveRequest(SaveFormat.MARKDOWN);
+ break;
case GridContentEvents.SaveAsExcel:
self.sendSaveRequest(SaveFormat.EXCEL);
break;
@@ -313,6 +316,9 @@ export abstract class GridParentComponent extends Disposable {
'SaveAsJSON': () => {
this.sendSaveRequest(SaveFormat.JSON);
},
+ 'SaveAsMarkdown': () => {
+ this.sendSaveRequest(SaveFormat.MARKDOWN);
+ },
'SaveAsExcel': () => {
this.sendSaveRequest(SaveFormat.EXCEL);
},
@@ -341,6 +347,9 @@ export abstract class GridParentComponent extends Disposable {
case 'savejson':
this.dataService.sendSaveRequest({ batchIndex: event.batchId, resultSetNumber: event.resultId, format: SaveFormat.JSON, selection: event.selection });
break;
+ case 'saveMarkdown':
+ this.dataService.sendSaveRequest({ batchIndex: event.batchId, resultSetNumber: event.resultId, format: SaveFormat.MARKDOWN, selection: event.selection });
+ break;
case 'saveexcel':
this.dataService.sendSaveRequest({ batchIndex: event.batchId, resultSetNumber: event.resultId, format: SaveFormat.EXCEL, selection: event.selection });
break;
diff --git a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts
index 31230226fe..4afcb191bd 100644
--- a/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts
+++ b/src/sql/workbench/contrib/notebook/browser/outputs/gridOutput.component.ts
@@ -276,6 +276,7 @@ class DataResourceTable extends GridTableBase {
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
+ this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEMARKDOWN_ID, SaveResultAction.SAVEMARKDOWN_LABEL, SaveResultAction.SAVEMARKDOWN_ICON, SaveFormat.MARKDOWN),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
this.instantiationService.createInstance(NotebookChartAction, this)
];
diff --git a/src/sql/workbench/contrib/query/browser/actions.ts b/src/sql/workbench/contrib/query/browser/actions.ts
index 4e737194ca..eb5da31267 100644
--- a/src/sql/workbench/contrib/query/browser/actions.ts
+++ b/src/sql/workbench/contrib/query/browser/actions.ts
@@ -52,6 +52,10 @@ export class SaveResultAction extends Action {
public static SAVEJSON_LABEL = localize('saveAsJson', "Save As JSON");
public static SAVEJSON_ICON = 'saveJson';
+ public static SAVEMARKDOWN_ID = 'grid.saveAsMarkdown';
+ public static SAVEMARKDOWN_LABEL = localize('saveAsMarkdown', "Save As Markdown");
+ public static SAVEMARKDOWN_ICON = 'saveMarkdown';
+
public static SAVEEXCEL_ID = 'grid.saveAsExcel';
public static SAVEEXCEL_LABEL = localize('saveAsExcel', "Save As Excel");
public static SAVEEXCEL_ICON = 'saveExcel';
diff --git a/src/sql/workbench/contrib/query/browser/gridPanel.ts b/src/sql/workbench/contrib/query/browser/gridPanel.ts
index 8bcff02ff8..7712830be9 100644
--- a/src/sql/workbench/contrib/query/browser/gridPanel.ts
+++ b/src/sql/workbench/contrib/query/browser/gridPanel.ts
@@ -953,6 +953,7 @@ class GridTable extends GridTableBase {
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
+ this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEMARKDOWN_ID, SaveResultAction.SAVEMARKDOWN_LABEL, SaveResultAction.SAVEMARKDOWN_ICON, SaveFormat.MARKDOWN),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
this.instantiationService.createInstance(ChartDataAction)
);
@@ -969,6 +970,7 @@ class GridTable extends GridTableBase {
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVECSV_ID, SaveResultAction.SAVECSV_LABEL, SaveResultAction.SAVECSV_ICON, SaveFormat.CSV),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEEXCEL_ID, SaveResultAction.SAVEEXCEL_LABEL, SaveResultAction.SAVEEXCEL_ICON, SaveFormat.EXCEL),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEJSON_ID, SaveResultAction.SAVEJSON_LABEL, SaveResultAction.SAVEJSON_ICON, SaveFormat.JSON),
+ this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEMARKDOWN_ID, SaveResultAction.SAVEMARKDOWN_LABEL, SaveResultAction.SAVEMARKDOWN_ICON, SaveFormat.MARKDOWN),
this.instantiationService.createInstance(SaveResultAction, SaveResultAction.SAVEXML_ID, SaveResultAction.SAVEXML_LABEL, SaveResultAction.SAVEXML_ICON, SaveFormat.XML),
];
}
diff --git a/src/sql/workbench/contrib/query/browser/query.contribution.ts b/src/sql/workbench/contrib/query/browser/query.contribution.ts
index 73b2289ece..93507adba0 100644
--- a/src/sql/workbench/contrib/query/browser/query.contribution.ts
+++ b/src/sql/workbench/contrib/query/browser/query.contribution.ts
@@ -287,6 +287,14 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
handler: gridCommands.saveAsJson
});
+KeybindingsRegistry.registerCommandAndKeybindingRule({
+ id: gridActions.GRID_SAVEMARKDOWN_ID,
+ weight: KeybindingWeight.EditorContrib,
+ when: ResultsGridFocusCondition,
+ primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyR, KeyMod.CtrlCmd | KeyCode.KeyM),
+ handler: gridCommands.saveAsMarkdown
+});
+
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: gridActions.GRID_SAVEEXCEL_ID,
weight: KeybindingWeight.EditorContrib,
@@ -384,6 +392,21 @@ const queryEditorConfiguration: IConfigurationNode = {
'description': localize('queryEditor.results.saveAsCsv.encoding', "File encoding used when saving results as CSV"),
'default': 'utf-8'
},
+ 'queryEditor.results.saveAsMarkdown.encoding': {
+ 'type': 'string',
+ 'description': localize('queryEditor.results.saveAsMarkdown.encoding', "File encoding used when saving results as Markdown"),
+ 'default': 'utf-8'
+ },
+ 'queryEditor.results.saveAsMarkdown.includeHeaders': {
+ 'type': 'boolean',
+ 'description': localize('queryEditor.results.saveAsMarkdown.includeHeaders', "When true, column headers are included when saving results as a Markdown file"),
+ 'default': true
+ },
+ 'queryEditor.results.saveAsMarkdown.lineSeparator': {
+ 'type': 'string',
+ 'description': localize('queryEditor.results.saveAsMarkdown.lineSeparator', "Character(s) to use to separate lines when exporting to Markdown, defaults to system line endings"),
+ 'default': null
+ },
'queryEditor.results.saveAsXml.formatted': {
'type': 'boolean',
'description': localize('queryEditor.results.saveAsXml.formatted', "When true, XML output will be formatted when saving results as XML"),
diff --git a/src/sql/workbench/services/query/common/gridContentEvents.ts b/src/sql/workbench/services/query/common/gridContentEvents.ts
index 5d39850433..61fba5e0f0 100644
--- a/src/sql/workbench/services/query/common/gridContentEvents.ts
+++ b/src/sql/workbench/services/query/common/gridContentEvents.ts
@@ -14,6 +14,7 @@ export const SelectAll = 'SelectAll';
export const SelectAllMessages = 'SelectAllMessages';
export const SaveAsCsv = 'SaveAsCSV';
export const SaveAsJSON = 'SaveAsJSON';
+export const SaveAsMarkdown = 'SaveAsMarkdown';
export const SaveAsExcel = 'SaveAsExcel';
export const SaveAsXML = 'SaveAsXML';
export const ViewAsChart = 'ViewAsChart';
diff --git a/src/sql/workbench/services/query/common/resultSerializer.ts b/src/sql/workbench/services/query/common/resultSerializer.ts
index 18e9429dfb..d61428c249 100644
--- a/src/sql/workbench/services/query/common/resultSerializer.ts
+++ b/src/sql/workbench/services/query/common/resultSerializer.ts
@@ -38,6 +38,7 @@ export interface SaveResultsResponse {
export enum SaveFormat {
CSV = 'csv',
JSON = 'json',
+ MARKDOWN = 'markdown',
EXCEL = 'excel',
XML = 'xml'
}
@@ -128,6 +129,9 @@ export class ResultSerializer {
case SaveFormat.JSON:
fileName = fileName + '.json';
break;
+ case SaveFormat.MARKDOWN:
+ fileName = fileName + '.md';
+ break;
case SaveFormat.EXCEL:
fileName = fileName + '.xlsx';
break;
@@ -153,6 +157,10 @@ export class ResultSerializer {
fileFilter.name = nls.localize('resultsSerializer.saveAsFileExtensionJSONTitle', "JSON");
fileFilter.extensions = ['json'];
break;
+ case SaveFormat.MARKDOWN:
+ fileFilter.name = nls.localize('resultsSerializer.saveAsFileExtensionMarkdownTitle', "Markdown");
+ fileFilter.extensions = ['md'];
+ break;
case SaveFormat.EXCEL:
fileFilter.name = nls.localize('resultsSerializer.saveAsFileExtensionExcelTitle', "Excel Workbook");
fileFilter.extensions = ['xlsx'];
@@ -170,22 +178,21 @@ export class ResultSerializer {
return fileFilters;
}
- public getBasicSaveParameters(format: string): SaveResultsRequestParams {
- let saveResultsParams: SaveResultsRequestParams;
-
- if (format === SaveFormat.CSV) {
- saveResultsParams = this.getConfigForCsv();
- } else if (format === SaveFormat.JSON) {
- saveResultsParams = this.getConfigForJson();
- } else if (format === SaveFormat.EXCEL) {
- saveResultsParams = this.getConfigForExcel();
- } else if (format === SaveFormat.XML) {
- saveResultsParams = this.getConfigForXml();
+ public getBasicSaveParameters(format: SaveFormat): SaveResultsRequestParams {
+ switch (format) {
+ case SaveFormat.CSV:
+ return this.getConfigForCsv();
+ case SaveFormat.EXCEL:
+ return this.getConfigForExcel();
+ case SaveFormat.JSON:
+ return this.getConfigForJson();
+ case SaveFormat.MARKDOWN:
+ return this.getConfigForMarkdown();
+ case SaveFormat.XML:
+ return this.getConfigForXml();
}
- return saveResultsParams!; // this could be unsafe
}
-
private getConfigForCsv(): SaveResultsRequestParams {
let saveResultsParams = { resultFormat: SaveFormat.CSV as string };
@@ -219,6 +226,26 @@ export class ResultSerializer {
return { resultFormat: SaveFormat.JSON as string };
}
+ private getConfigForMarkdown(): SaveResultsRequestParams {
+ let saveResultsParams = { resultFormat: SaveFormat.MARKDOWN as string };
+
+ // Get config from VSCode config and build params with it if user has set config
+ const saveConfig = this._configurationService.getValue('queryEditor').results.saveAsMarkdown;
+ if (saveConfig) {
+ if (saveConfig.encoding) {
+ saveResultsParams.encoding = saveConfig.encoding;
+ }
+ if (saveConfig.includeHeaders !== undefined) {
+ saveResultsParams.includeHeaders = saveConfig.includeHeaders;
+ }
+ if (saveConfig.lineSeparator !== undefined) {
+ saveResultsParams.lineSeperator = saveConfig.lineSeparator;
+ }
+ }
+
+ return saveResultsParams;
+ }
+
private getConfigForExcel(): SaveResultsRequestParams {
let saveResultsParams = { resultFormat: SaveFormat.EXCEL as string };
@@ -260,7 +287,7 @@ export class ResultSerializer {
filePath: URI,
batchIndex: number,
resultSetNo: number,
- format: string,
+ format: SaveFormat,
selection?: Slick.Range
): SaveResultsRequestParams {
let saveResultsParams = this.getBasicSaveParameters(format);