Skip to content

Commit 6735dac

Browse files
authored
Merge pull request #54 from lumaxis/feature/add-markdown-settings
New: Add option to configure Markdown block behavior
2 parents e09bc86 + 7017f81 commit 6735dac

File tree

7 files changed

+207
-35
lines changed

7 files changed

+207
-35
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
- Rename VS Code commands to be shorter and more concise.
88
- `Copy Snippet Without Leading Indentation` is now `Copy Snippet`
99
- `Copy Snippet Without Leading Indentation as Markdown Code Block` is now `Copy Snippet as Markdown Code Block`
10+
- Improve configuration options for Markdown code blocks
11+
- Add `markdownCodeBlock.includeLanguageIdentifier` configuration option for Markdown block behavior. **This replaces `snippet-copy.addLanguageIdentifierToMarkdownBlock`**.
12+
- Allows to set whether Markdown blocks should always include the [language identifier](https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks) which allows for syntax highlighting in some tools (for example github.com) but is not compatible with others (for example Slack).
13+
- The default is "`prompt`" which makes the extension always prompt when using the "Copy Snippet as Markdown Code Block" command.
1014

1115
## [0.2.3]
1216

package-lock.json

Lines changed: 65 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,20 @@
4242
"configuration": {
4343
"title": "Snippet Copy",
4444
"properties": {
45-
"snippet-copy.addLanguageIdentifierToMarkdownBlock": {
46-
"type": "boolean",
47-
"default": false,
48-
"description": "Add a programming language identifier to the beginning of the Markdown code block. This is incompatible with some apps, for example Slack."
45+
"snippet-copy.markdownCodeBlock.includeLanguageIdentifier": {
46+
"type": "string",
47+
"default": "prompt",
48+
"markdownDescription": "Style of the generated [fenced Markdown code block](https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks). By default, prompts every time a snippet is copied as a Markdown code block.",
49+
"enum": [
50+
"never",
51+
"always",
52+
"prompt"
53+
],
54+
"markdownEnumDescriptions": [
55+
"Copy a regular fenced Markdown code block without language identifier.",
56+
"Copy a fenced Markdown code block that includes the language identifier of the current document. \nThis enables [automatic syntax highlighting](https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks#syntax-highlighting) on e.g. GitHub or StackOverflow but isn't compatible with some apps, for example Slack.",
57+
"Always prompt when copying a snippet as a Markdown code block whether to include the language identifier or not."
58+
]
4959
}
5060
}
5161
},
@@ -98,6 +108,7 @@
98108
"eslint": "^7.3.1",
99109
"glob": "^7.1.6",
100110
"mocha": "^8.0.1",
111+
"testdouble": "^3.13.1",
101112
"typescript": "^3.9.5",
102113
"vscode-test": "^1.4.0"
103114
}

src/extension.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,19 @@ import { generateSnippet } from './lib/textHelpers';
44
export function activate(context: ExtensionContext): void {
55
context.subscriptions.push(
66
commands.registerTextEditorCommand('snippet-copy.copySnippet', (editor) => {
7-
const snippet = generateSnippet(editor.document, editor.selections, false);
8-
9-
void env.clipboard.writeText(snippet);
7+
void generateSnippet(editor.document, editor.selections, false)
8+
.then((snippet) => {
9+
return env.clipboard.writeText(snippet);
10+
});
1011
})
1112
);
13+
1214
context.subscriptions.push(
1315
commands.registerTextEditorCommand('snippet-copy.copySnippetAsMarkdownCodeBlock', (editor) => {
14-
const snippet = generateSnippet(editor.document, editor.selections, true);
15-
16-
void env.clipboard.writeText(snippet);
16+
void generateSnippet(editor.document, editor.selections, true)
17+
.then((snippet) => {
18+
return env.clipboard.writeText(snippet);
19+
});
1720
})
1821
);
1922
}

src/lib/textHelpers.ts

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1-
import { Selection, TextDocument, workspace } from "vscode";
1+
import { QuickPickItem, Selection, TextDocument, window, workspace } from "vscode";
2+
import { ExtensionConfig, IncludeLanguageIdentifier } from "../types/config";
23
import { contentOfLinesWithAdjustedIndentation, endOfLineCharacter, languageId, minimumIndentationForLineIndexes } from "./documentHelpers";
34
import { lineIndexesForSelection } from "./selectionHelpers";
45

5-
export function generateSnippet(document: TextDocument, selections: Selection[], markdownCodeBlock = false): string {
6+
type MarkdownCodeBlockFlavorQuickPickItems = QuickPickItem & {
7+
detail: IncludeLanguageIdentifier;
8+
};
9+
10+
export async function generateSnippet(document: TextDocument, selections: Selection[], wrapInMarkdownCodeBlock = false): Promise<string> {
611
const texts: string[] = [];
712
selections.forEach(selection => {
813
texts.push(generateCopyableText(document, selection));
914
});
1015

1116
const snippet = texts.join(endOfLineCharacter(document));
1217

13-
if (markdownCodeBlock) {
14-
const config = workspace.getConfiguration('snippet-copy');
15-
16-
return wrapTextInMarkdownCodeBlock(document, snippet, config.addLanguageIdentifierToMarkdownBlock);
18+
if (wrapInMarkdownCodeBlock) {
19+
return wrapTextInMarkdownCodeBlock(document, snippet, await includeLanguageIdentifier(workspace.getConfiguration('snippet-copy') as ExtensionConfig));
1720
}
1821

1922
return snippet;
@@ -38,7 +41,42 @@ export function wrapTextInMarkdownCodeBlock(document: TextDocument, text: string
3841
const eolCharacter = endOfLineCharacter(document);
3942
const optionalLanguageIdentifier = addLanguageId ? languageId(document) : '';
4043

41-
return codeBlockDelimiter + optionalLanguageIdentifier + eolCharacter +
42-
text + eolCharacter +
43-
codeBlockDelimiter;
44+
return codeBlockDelimiter + optionalLanguageIdentifier + eolCharacter +
45+
text + eolCharacter +
46+
codeBlockDelimiter;
47+
}
48+
49+
export async function includeLanguageIdentifier(config: ExtensionConfig): Promise<boolean> {
50+
let includeLanguageIdentifier = config.markdownCodeBlock.includeLanguageIdentifier;
51+
52+
if (includeLanguageIdentifier === 'prompt') {
53+
const prompt = await promptForMarkdownCodeBlockFlavor();
54+
55+
if (prompt && isMarkdownCodeBlockFlavor(prompt.detail)) {
56+
includeLanguageIdentifier = prompt.detail;
57+
}
58+
}
59+
return includeLanguageIdentifier === 'always';
60+
}
61+
62+
export async function promptForMarkdownCodeBlockFlavor(): Promise<MarkdownCodeBlockFlavorQuickPickItems | undefined> {
63+
const quickPickItems: MarkdownCodeBlockFlavorQuickPickItems[] = [
64+
{
65+
label: 'Plain fenced Markdown code block',
66+
detail: 'never',
67+
description: 'Copy a regular fenced Markdown code block without language identifier'
68+
}, {
69+
label: 'Include Markdown language identifier',
70+
detail: 'always',
71+
description: "Copy a Markdown code block that includes the language identifier of the current document"
72+
}
73+
];
74+
return window.showQuickPick(quickPickItems, {
75+
matchOnDetail: true
76+
});
77+
}
78+
79+
export function isMarkdownCodeBlockFlavor(value: string | undefined): value is IncludeLanguageIdentifier {
80+
const validValues: IncludeLanguageIdentifier[] = ['never', 'always'];
81+
return !!value && validValues.includes(value as IncludeLanguageIdentifier);
4482
}

src/test/suite/lib/textHelpers.test.ts

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import * as assert from 'assert';
22
import * as path from 'path';
3+
import * as td from 'testdouble';
34
import * as vscode from 'vscode';
45
import { Position, Selection, TextDocument } from 'vscode';
5-
import { generateCopyableText, generateSnippet, wrapTextInMarkdownCodeBlock } from '../../../lib/textHelpers';
6+
import { generateCopyableText, generateSnippet, includeLanguageIdentifier, isMarkdownCodeBlockFlavor, wrapTextInMarkdownCodeBlock } from '../../../lib/textHelpers';
7+
import { ExtensionConfig } from '../../../types/config';
68

79
const fixturesPath = '/../../../../src/test/fixtures/';
810
const uri = vscode.Uri.file(
@@ -34,19 +36,19 @@ describe('Text Helpers', () => {
3436
});
3537

3638
context('generateSnippet', () => {
37-
it('generates the correct snippet for a single selection', () => {
38-
assert.deepEqual(testSelection1.content, generateSnippet(document, [testSelection1.selection]));
39+
it('generates the correct snippet for a single selection', async () => {
40+
assert.deepEqual(testSelection1.content, await generateSnippet(document, [testSelection1.selection]));
3941
});
4042

41-
it('generates the correct snippet for multiple selections', () => {
43+
it('generates the correct snippet for multiple selections', async () => {
4244
assert.deepEqual(testSelection1.content + '\n' + testSelection2.content,
43-
generateSnippet(document, [testSelection1.selection, testSelection2.selection])
45+
await generateSnippet(document, [testSelection1.selection, testSelection2.selection])
4446
);
4547
});
4648

47-
it('generates the correct snippet for multiple selections where one ends on the beginning of a newline', () => {
49+
it('generates the correct snippet for multiple selections where one ends on the beginning of a newline', async () => {
4850
assert.deepEqual(testSelection1.content + '\n' + testSelection2.content,
49-
generateSnippet(document, [
51+
await generateSnippet(document, [
5052
new Selection(testSelection1.selection.start, new Position(5, 0)),
5153
testSelection2.selection
5254
])
@@ -70,17 +72,56 @@ describe('Text Helpers', () => {
7072
generateCopyableText(document, testSelection3.selection)
7173
);
7274
});
75+
});
76+
77+
context('wrapTextInMarkdownCodeBlock', () => {
78+
it('returns the text wrapped in a Markdown code block', () => {
79+
const codeSnippet = 'console.log("Yo");';
80+
assert.equal(wrapTextInMarkdownCodeBlock(document, codeSnippet), '```\n' + codeSnippet + '\n```');
81+
});
82+
83+
it('returns the wrapped text with a language identifier', () => {
84+
const codeSnippet = 'console.log("Yo");';
85+
assert.equal(wrapTextInMarkdownCodeBlock(document, codeSnippet, true), '```javascript\n' + codeSnippet + '\n```');
86+
});
87+
});
88+
89+
context('includeLanguageIdentifier', () => {
90+
it('returns true if setting is "includeLanguageIdentifier"', async () => {
91+
const config: unknown = td.object({ markdownCodeBlock: { includeLanguageIdentifier: 'always' } });
92+
93+
assert.strictEqual(await includeLanguageIdentifier(config as ExtensionConfig), true);
94+
});
95+
96+
it('returns false if setting is "plain"', async () => {
97+
const config: unknown = td.object({ markdownCodeBlock: { includeLanguageIdentifier: 'never' } });
98+
99+
assert.strictEqual(await includeLanguageIdentifier(config as ExtensionConfig), false);
100+
});
101+
102+
it('returns false if setting is true', async () => {
103+
const config: unknown = td.object({ markdownCodeBlock: { includeLanguageIdentifier: true } });
104+
105+
assert.strictEqual(await includeLanguageIdentifier(config as ExtensionConfig), false);
106+
});
107+
});
73108

74-
context('wrapTextInMarkdownCodeBlock', () => {
75-
it('returns the text wrapped in a Markdown code block', () => {
76-
const codeSnippet = 'console.log("Yo");';
77-
assert.equal(wrapTextInMarkdownCodeBlock(document, codeSnippet), '```\n' + codeSnippet + '\n```');
78-
});
109+
context('isMarkdownCodeBlockFlavor', () => {
110+
it('returns true if value is "never"', () => {
111+
assert.equal(isMarkdownCodeBlockFlavor('never'), true);
112+
});
79113

80-
it('returns the wrapped text with a language identifier', () => {
81-
const codeSnippet = 'console.log("Yo");';
82-
assert.equal(wrapTextInMarkdownCodeBlock(document, codeSnippet, true), '```javascript\n' + codeSnippet + '\n```');
83-
});
114+
it('returns true if value is "always"', () => {
115+
assert.equal(isMarkdownCodeBlockFlavor('always'), true);
84116
});
117+
118+
it('returns false if value is any other string', () => {
119+
assert.equal(isMarkdownCodeBlockFlavor('prompt'), false);
120+
});
121+
122+
it('returns false if value is undefined', () => {
123+
assert.equal(isMarkdownCodeBlockFlavor(undefined), false);
124+
});
125+
85126
});
86127
});

src/types/config.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { WorkspaceConfiguration } from "vscode";
2+
3+
export type IncludeLanguageIdentifier = 'always' | 'never';
4+
5+
export type ExtensionConfig = WorkspaceConfiguration & {
6+
markdownCodeBlock: {
7+
includeLanguageIdentifier: IncludeLanguageIdentifier | 'prompt'
8+
};
9+
addLanguageIdentifierToMarkdownBlock?: boolean;
10+
};

0 commit comments

Comments
 (0)