mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 21:55:38 +00:00
Add commands to insert images/links in markdown (#163706)
* Add commands to insert images/links in markdown Fixes #162809 * Rename commands and allow passing in uris * Support selecting many images/files
This commit is contained in:
parent
129dbaa32b
commit
641046a11d
|
@ -30,6 +30,8 @@
|
|||
"onCommand:markdown.api.render",
|
||||
"onCommand:markdown.api.reloadPlugins",
|
||||
"onCommand:markdown.findAllFileReferences",
|
||||
"onCommand:markdown.editor.insertLinkFromWorkspace",
|
||||
"onCommand:markdown.editor.insertImageFromWorkspace",
|
||||
"onWebviewPanel:markdown.preview",
|
||||
"onCustomEditor:vscode.markdown.preview.editor"
|
||||
],
|
||||
|
@ -175,6 +177,18 @@
|
|||
"command": "markdown.findAllFileReferences",
|
||||
"title": "%markdown.findAllFileReferences%",
|
||||
"category": "Markdown"
|
||||
},
|
||||
{
|
||||
"command": "markdown.editor.insertLinkFromWorkspace",
|
||||
"title": "%markdown.editor.insertLinkFromWorkspace%",
|
||||
"category": "Markdown",
|
||||
"enablement": "editorLangId == markdown"
|
||||
},
|
||||
{
|
||||
"command": "markdown.editor.insertImageFromWorkspace",
|
||||
"title": "%markdown.editor.insertImageFromWorkspace%",
|
||||
"category": "Markdown",
|
||||
"enablement": "editorLangId == markdown"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
"markdown.preview.refresh.title": "Refresh Preview",
|
||||
"markdown.preview.toggleLock.title": "Toggle Preview Locking",
|
||||
"markdown.findAllFileReferences": "Find File References",
|
||||
"markdown.editor.insertLinkFromWorkspace": "Insert Link to File in Workspace",
|
||||
"markdown.editor.insertImageFromWorkspace": "Insert Image from Workspace",
|
||||
"configuration.markdown.preview.openMarkdownLinks.description": "Controls how links to other Markdown files in the Markdown preview should be opened.",
|
||||
"configuration.markdown.preview.openMarkdownLinks.inEditor": "Try to open links in the editor.",
|
||||
"configuration.markdown.preview.openMarkdownLinks.inPreview": "Try to open links in the Markdown preview.",
|
||||
|
|
|
@ -3,11 +3,41 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export { RefreshPreviewCommand } from './refreshPreview';
|
||||
export { ReloadPlugins } from './reloadPlugins';
|
||||
export { RenderDocument } from './renderDocument';
|
||||
export { ShowLockedPreviewToSideCommand, ShowPreviewCommand, ShowPreviewToSideCommand } from './showPreview';
|
||||
export { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector';
|
||||
export { ShowSourceCommand } from './showSource';
|
||||
export { ToggleLockCommand } from './toggleLock';
|
||||
import * as vscode from 'vscode';
|
||||
import { CommandManager } from '../commandManager';
|
||||
import { MarkdownItEngine } from '../markdownEngine';
|
||||
import { MarkdownPreviewManager } from '../preview/previewManager';
|
||||
import { ContentSecurityPolicyArbiter, PreviewSecuritySelector } from '../preview/security';
|
||||
import { TelemetryReporter } from '../telemetryReporter';
|
||||
import { InsertLinkFromWorkspace, InsertImageFromWorkspace } from './insertResource';
|
||||
import { RefreshPreviewCommand } from './refreshPreview';
|
||||
import { ReloadPlugins } from './reloadPlugins';
|
||||
import { RenderDocument } from './renderDocument';
|
||||
import { ShowLockedPreviewToSideCommand, ShowPreviewCommand, ShowPreviewToSideCommand } from './showPreview';
|
||||
import { ShowPreviewSecuritySelectorCommand } from './showPreviewSecuritySelector';
|
||||
import { ShowSourceCommand } from './showSource';
|
||||
import { ToggleLockCommand } from './toggleLock';
|
||||
|
||||
export function registerMarkdownCommands(
|
||||
commandManager: CommandManager,
|
||||
previewManager: MarkdownPreviewManager,
|
||||
telemetryReporter: TelemetryReporter,
|
||||
cspArbiter: ContentSecurityPolicyArbiter,
|
||||
engine: MarkdownItEngine,
|
||||
): vscode.Disposable {
|
||||
const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager);
|
||||
|
||||
commandManager.register(new ShowPreviewCommand(previewManager, telemetryReporter));
|
||||
commandManager.register(new ShowPreviewToSideCommand(previewManager, telemetryReporter));
|
||||
commandManager.register(new ShowLockedPreviewToSideCommand(previewManager, telemetryReporter));
|
||||
commandManager.register(new ShowSourceCommand(previewManager));
|
||||
commandManager.register(new RefreshPreviewCommand(previewManager, engine));
|
||||
commandManager.register(new ShowPreviewSecuritySelectorCommand(previewSecuritySelector, previewManager));
|
||||
commandManager.register(new ToggleLockCommand(previewManager));
|
||||
commandManager.register(new RenderDocument(engine));
|
||||
commandManager.register(new ReloadPlugins(previewManager, engine));
|
||||
commandManager.register(new InsertLinkFromWorkspace());
|
||||
commandManager.register(new InsertImageFromWorkspace());
|
||||
|
||||
return commandManager;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command } from '../commandManager';
|
||||
import { createUriListSnippet, getParentDocumentUri, imageFileExtensions } from '../languageFeatures/dropIntoEditor';
|
||||
import { coalesce } from '../util/arrays';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class InsertLinkFromWorkspace implements Command {
|
||||
public readonly id = 'markdown.editor.insertLinkFromWorkspace';
|
||||
|
||||
public async execute(resources?: vscode.Uri[]) {
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
resources ??= await vscode.window.showOpenDialog({
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: true,
|
||||
openLabel: localize('insertLink.openLabel', "Insert link"),
|
||||
title: localize('insertLink.title', "Insert link"),
|
||||
defaultUri: getParentDocumentUri(activeEditor.document),
|
||||
});
|
||||
|
||||
return insertLink(activeEditor, resources ?? [], false);
|
||||
}
|
||||
}
|
||||
|
||||
export class InsertImageFromWorkspace implements Command {
|
||||
public readonly id = 'markdown.editor.insertImageFromWorkspace';
|
||||
|
||||
public async execute(resources?: vscode.Uri[]) {
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
resources ??= await vscode.window.showOpenDialog({
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: true,
|
||||
filters: {
|
||||
[localize('insertImage.imagesLabel', "Images")]: Array.from(imageFileExtensions)
|
||||
},
|
||||
openLabel: localize('insertImage.openLabel', "Insert image"),
|
||||
title: localize('insertImage.title', "Insert image"),
|
||||
defaultUri: getParentDocumentUri(activeEditor.document),
|
||||
});
|
||||
|
||||
return insertLink(activeEditor, resources ?? [], true);
|
||||
}
|
||||
}
|
||||
|
||||
async function insertLink(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsImage: boolean): Promise<void> {
|
||||
if (!selectedFiles.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const edit = createInsertLinkEdit(activeEditor, selectedFiles, insertAsImage);
|
||||
await vscode.workspace.applyEdit(edit);
|
||||
}
|
||||
|
||||
function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsImage: boolean) {
|
||||
const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => {
|
||||
const selectionText = activeEditor.document.getText(selection);
|
||||
const snippet = createUriListSnippet(activeEditor.document, selectedFiles, {
|
||||
insertAsImage: insertAsImage,
|
||||
placeholderText: selectionText,
|
||||
placeholderStartIndex: (i + 1) * selectedFiles.length,
|
||||
});
|
||||
|
||||
return snippet ? new vscode.SnippetTextEdit(selection, snippet) : undefined;
|
||||
}));
|
||||
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
edit.set(activeEditor.document.uri, snippetEdits);
|
||||
return edit;
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
import * as vscode from 'vscode';
|
||||
import { MdLanguageClient } from './client/client';
|
||||
import { CommandManager } from './commandManager';
|
||||
import * as commands from './commands/index';
|
||||
import { registerMarkdownCommands } from './commands/index';
|
||||
import { registerPasteSupport } from './languageFeatures/copyPaste';
|
||||
import { registerDiagnosticSupport } from './languageFeatures/diagnostics';
|
||||
import { registerDropIntoEditorSupport } from './languageFeatures/dropIntoEditor';
|
||||
|
@ -17,8 +17,8 @@ import { MarkdownItEngine } from './markdownEngine';
|
|||
import { MarkdownContributionProvider } from './markdownExtensions';
|
||||
import { MdDocumentRenderer } from './preview/documentRenderer';
|
||||
import { MarkdownPreviewManager } from './preview/previewManager';
|
||||
import { ContentSecurityPolicyArbiter, ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './preview/security';
|
||||
import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter';
|
||||
import { ExtensionContentSecurityPolicyArbiter } from './preview/security';
|
||||
import { loadDefaultTelemetryReporter } from './telemetryReporter';
|
||||
import { MdLinkOpener } from './util/openDocumentLink';
|
||||
|
||||
export function activateShared(
|
||||
|
@ -63,23 +63,3 @@ function registerMarkdownLanguageFeatures(
|
|||
);
|
||||
}
|
||||
|
||||
function registerMarkdownCommands(
|
||||
commandManager: CommandManager,
|
||||
previewManager: MarkdownPreviewManager,
|
||||
telemetryReporter: TelemetryReporter,
|
||||
cspArbiter: ContentSecurityPolicyArbiter,
|
||||
engine: MarkdownItEngine,
|
||||
): vscode.Disposable {
|
||||
const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager);
|
||||
|
||||
commandManager.register(new commands.ShowPreviewCommand(previewManager, telemetryReporter));
|
||||
commandManager.register(new commands.ShowPreviewToSideCommand(previewManager, telemetryReporter));
|
||||
commandManager.register(new commands.ShowLockedPreviewToSideCommand(previewManager, telemetryReporter));
|
||||
commandManager.register(new commands.ShowSourceCommand(previewManager));
|
||||
commandManager.register(new commands.RefreshPreviewCommand(previewManager, engine));
|
||||
commandManager.register(new commands.ShowPreviewSecuritySelectorCommand(previewSecuritySelector, previewManager));
|
||||
commandManager.register(new commands.ToggleLockCommand(previewManager));
|
||||
commandManager.register(new commands.RenderDocument(engine));
|
||||
commandManager.register(new commands.ReloadPlugins(previewManager, engine));
|
||||
return commandManager;
|
||||
}
|
||||
|
|
|
@ -8,20 +8,20 @@ import * as vscode from 'vscode';
|
|||
import * as URI from 'vscode-uri';
|
||||
import { Schemes } from '../util/schemes';
|
||||
|
||||
const imageFileExtensions = new Set<string>([
|
||||
'.bmp',
|
||||
'.gif',
|
||||
'.ico',
|
||||
'.jpe',
|
||||
'.jpeg',
|
||||
'.jpg',
|
||||
'.png',
|
||||
'.psd',
|
||||
'.svg',
|
||||
'.tga',
|
||||
'.tif',
|
||||
'.tiff',
|
||||
'.webp',
|
||||
export const imageFileExtensions = new Set<string>([
|
||||
'bmp',
|
||||
'gif',
|
||||
'ico',
|
||||
'jpe',
|
||||
'jpeg',
|
||||
'jpg',
|
||||
'png',
|
||||
'psd',
|
||||
'svg',
|
||||
'tga',
|
||||
'tif',
|
||||
'tiff',
|
||||
'webp',
|
||||
]);
|
||||
|
||||
export function registerDropIntoEditorSupport(selector: vscode.DocumentSelector) {
|
||||
|
@ -56,7 +56,20 @@ export async function tryGetUriListSnippet(document: vscode.TextDocument, dataTr
|
|||
return createUriListSnippet(document, uris);
|
||||
}
|
||||
|
||||
export function createUriListSnippet(document: vscode.TextDocument, uris: readonly vscode.Uri[]): vscode.SnippetString | undefined {
|
||||
interface UriListSnippetOptions {
|
||||
readonly placeholderText?: string;
|
||||
|
||||
readonly placeholderStartIndex?: number;
|
||||
|
||||
/**
|
||||
* Should the snippet be for an image?
|
||||
*
|
||||
* If `undefined`, tries to infer this from the uri.
|
||||
*/
|
||||
readonly insertAsImage?: boolean;
|
||||
}
|
||||
|
||||
export function createUriListSnippet(document: vscode.TextDocument, uris: readonly vscode.Uri[], options?: UriListSnippetOptions): vscode.SnippetString | undefined {
|
||||
if (!uris.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
@ -69,9 +82,15 @@ export function createUriListSnippet(document: vscode.TextDocument, uris: readon
|
|||
? encodeURI(path.relative(dir.fsPath, uri.fsPath).replace(/\\/g, '/'))
|
||||
: uri.toString(false);
|
||||
|
||||
const ext = URI.Utils.extname(uri).toLowerCase();
|
||||
snippet.appendText(imageFileExtensions.has(ext) ? '![' : '[');
|
||||
snippet.appendTabstop();
|
||||
const ext = URI.Utils.extname(uri).toLowerCase().replace('.', '');
|
||||
const insertAsImage = typeof options?.insertAsImage === 'undefined' ? imageFileExtensions.has(ext) : !!options.insertAsImage;
|
||||
|
||||
snippet.appendText(insertAsImage ? '![' : '[');
|
||||
|
||||
const placeholderText = options?.placeholderText ?? (insertAsImage ? 'Alt text' : 'label');
|
||||
const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : undefined;
|
||||
snippet.appendPlaceholder(placeholderText, placeholderIndex);
|
||||
|
||||
snippet.appendText(`](${mdPath})`);
|
||||
|
||||
if (i <= uris.length - 1 && uris.length > 1) {
|
||||
|
@ -89,7 +108,7 @@ function getDocumentDir(document: vscode.TextDocument): vscode.Uri | undefined {
|
|||
return URI.Utils.dirname(docUri);
|
||||
}
|
||||
|
||||
function getParentDocumentUri(document: vscode.TextDocument): vscode.Uri {
|
||||
export function getParentDocumentUri(document: vscode.TextDocument): vscode.Uri {
|
||||
if (document.uri.scheme === Schemes.notebookCell) {
|
||||
for (const notebook of vscode.workspace.notebookDocuments) {
|
||||
for (const cell of notebook.getCells()) {
|
||||
|
|
|
@ -2,6 +2,12 @@
|
|||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
/**
|
||||
* @returns New array with all falsy values removed. The original array IS NOT modified.
|
||||
*/
|
||||
export function coalesce<T>(array: ReadonlyArray<T | undefined | null>): T[] {
|
||||
return <T[]>array.filter(e => !!e);
|
||||
}
|
||||
|
||||
export function equals<T>(one: ReadonlyArray<T>, other: ReadonlyArray<T>, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean {
|
||||
if (one.length !== other.length) {
|
||||
|
|
Loading…
Reference in a new issue