Skip to content

Commit 1b1d6ca

Browse files
author
Marc Lipovsky
authored
Merge pull request #56 from marclipovsky/feature/sidebar-preview
New feature: Enable sidebar that preview all command outputs before applying commands.
2 parents ff1b7dc + e1c99e8 commit 1b1d6ca

File tree

5 files changed

+518
-256
lines changed

5 files changed

+518
-256
lines changed

package.json

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,36 +19,31 @@
1919
],
2020
"main": "./dist/extension.js",
2121
"activationEvents": [
22-
"onCommand:string-manipulation.titleize",
23-
"onCommand:string-manipulation.titleizeApStyle",
24-
"onCommand:string-manipulation.titleizeChicagoStyle",
25-
"onCommand:string-manipulation.camelize",
26-
"onCommand:string-manipulation.chop",
27-
"onCommand:string-manipulation.clean",
28-
"onCommand:string-manipulation.cleanDiacritics",
29-
"onCommand:string-manipulation.classify",
30-
"onCommand:string-manipulation.underscored",
31-
"onCommand:string-manipulation.dasherize",
32-
"onCommand:string-manipulation.snake",
33-
"onCommand:string-manipulation.screamingSnake",
34-
"onCommand:string-manipulation.humanize",
35-
"onCommand:string-manipulation.slugify",
36-
"onCommand:string-manipulation.reverse",
37-
"onCommand:string-manipulation.swapCase",
38-
"onCommand:string-manipulation.decapitalize",
39-
"onCommand:string-manipulation.capitalize",
40-
"onCommand:string-manipulation.sentence",
41-
"onCommand:string-manipulation.prune",
42-
"onCommand:string-manipulation.truncate",
43-
"onCommand:string-manipulation.repeat",
44-
"onCommand:string-manipulation.increment",
45-
"onCommand:string-manipulation.decrement",
46-
"onCommand:string-manipulation.duplicateAndIncrement",
47-
"onCommand:string-manipulation.duplicateAndDecrement",
48-
"onCommand:string-manipulation.sequence",
49-
"onCommand:string-manipulation.randomCase"
22+
"onCommand:string-manipulation.*",
23+
"onView:stringManipulationSidebar"
5024
],
5125
"contributes": {
26+
"configuration": {
27+
"type": "object",
28+
"title": "String Manipulation Configuration",
29+
"properties": {
30+
"stringManipulation.labs": {
31+
"type": "boolean",
32+
"default": false,
33+
"description": "Enable experimental String Manipulation Labs features."
34+
}
35+
}
36+
},
37+
"views": {
38+
"explorer": [
39+
{
40+
"type": "webview",
41+
"id": "stringManipulationSidebar",
42+
"name": "String Manipulation",
43+
"when": "config.stringManipulation.labs"
44+
}
45+
]
46+
},
5247
"commands": [
5348
{
5449
"title": "Titleize",

src/commands.ts

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import * as vscode from "vscode";
2+
import * as underscore from "underscore.string";
3+
const apStyleTitleCase = require("ap-style-title-case");
4+
const chicagoStyleTitleCase = require("chicago-capitalize");
5+
const slugify = require("@sindresorhus/slugify");
6+
7+
interface MultiSelectData {
8+
offset?: number;
9+
}
10+
11+
const defaultFunction = (commandName: string, option?: any) => (str: string) =>
12+
(underscore as any)[commandName](str, option);
13+
14+
const sequence = (str: string, multiselectData: MultiSelectData = {}) => {
15+
return str.replace(/-?\d+/g, (n) => {
16+
const isFirst = typeof multiselectData.offset !== "number";
17+
multiselectData.offset = isFirst
18+
? Number(n)
19+
: (multiselectData.offset || 0) + 1;
20+
return String(multiselectData.offset);
21+
});
22+
};
23+
24+
const increment = (str: string) =>
25+
str.replace(/-?\d+/g, (n) => String(Number(n) + 1));
26+
27+
const decrement = (str: string) =>
28+
str.replace(/-?\d+/g, (n) => String(Number(n) - 1));
29+
30+
const randomCase = (input: string): string => {
31+
let result = "";
32+
for (const char of input) {
33+
if (Math.random() < 0.5) {
34+
result += char.toLowerCase();
35+
} else {
36+
result += char.toUpperCase();
37+
}
38+
}
39+
return result;
40+
};
41+
42+
export type StringFunction = (
43+
str: string,
44+
multiselectData?: MultiSelectData
45+
) => string;
46+
export type CommandFunction =
47+
| StringFunction
48+
| ((...args: any[]) => StringFunction);
49+
50+
const commandNameFunctionMap: { [key: string]: CommandFunction } = {
51+
titleize: defaultFunction("titleize"),
52+
chop: (n: number) => defaultFunction("chop", n),
53+
classify: defaultFunction("classify"),
54+
clean: defaultFunction("clean"),
55+
cleanDiacritics: defaultFunction("cleanDiacritics"),
56+
underscored: defaultFunction("underscored"),
57+
dasherize: defaultFunction("dasherize"),
58+
humanize: defaultFunction("humanize"),
59+
reverse: defaultFunction("reverse"),
60+
decapitalize: defaultFunction("decapitalize"),
61+
capitalize: defaultFunction("capitalize"),
62+
sentence: defaultFunction("capitalize", true),
63+
camelize: (str: string) =>
64+
underscore.camelize(/[a-z]/.test(str) ? str : str.toLowerCase()),
65+
slugify: slugify,
66+
swapCase: defaultFunction("swapCase"),
67+
snake: (str: string) =>
68+
underscore
69+
.underscored(str)
70+
.replace(/([A-Z])[^A-Z]/g, " $1")
71+
.replace(/[^a-z]+/gi, " ")
72+
.trim()
73+
.replace(/\s/gi, "_"),
74+
screamingSnake: (str: string) =>
75+
underscore
76+
.underscored(str)
77+
.replace(/([A-Z])[^A-Z]/g, " $1")
78+
.replace(/[^a-z]+/gi, " ")
79+
.trim()
80+
.replace(/\s/gi, "_")
81+
.toUpperCase(),
82+
titleizeApStyle: apStyleTitleCase,
83+
titleizeChicagoStyle: chicagoStyleTitleCase,
84+
truncate: (n: number) => defaultFunction("truncate", n),
85+
prune: (n: number) => (str: string) => str.slice(0, n - 3).trim() + "...",
86+
repeat: (n: number) => defaultFunction("repeat", n),
87+
increment,
88+
decrement,
89+
duplicateAndIncrement: (str: string) => str + increment(str),
90+
duplicateAndDecrement: (str: string) => str + decrement(str),
91+
sequence,
92+
utf8ToChar: (str: string) =>
93+
str
94+
.match(/\\u[\dA-Fa-f]{4}/g)
95+
?.map((x) => x.slice(2))
96+
.map((x) => String.fromCharCode(parseInt(x, 16)))
97+
.join("") || "",
98+
charToUtf8: (str: string) =>
99+
str
100+
.split("")
101+
.map((x) => `\\u${x.charCodeAt(0).toString(16).padStart(4, "0")}`)
102+
.join(""),
103+
randomCase,
104+
};
105+
106+
const numberFunctionNames = [
107+
"increment",
108+
"decrement",
109+
"sequence",
110+
"duplicateAndIncrement",
111+
"duplicateAndDecrement",
112+
];
113+
114+
export const functionNamesWithArgument = [
115+
"chop",
116+
"truncate",
117+
"prune",
118+
"repeat",
119+
];
120+
121+
export const stringFunction = async (
122+
commandName: string,
123+
context: vscode.ExtensionContext,
124+
shouldApply = true
125+
): Promise<{ replacedSelections: string[] } | undefined> => {
126+
const editor = vscode.window.activeTextEditor;
127+
if (!editor) {
128+
return;
129+
}
130+
131+
const selectionMap: {
132+
[key: number]: { selection: vscode.Selection; replaced: string };
133+
} = {};
134+
135+
let multiselectData: MultiSelectData = {};
136+
137+
let stringFunc: (str: string) => string;
138+
139+
let replacedSelections = [];
140+
141+
if (functionNamesWithArgument.includes(commandName)) {
142+
const valueStr = await vscode.window.showInputBox();
143+
if (valueStr === undefined) {
144+
return;
145+
}
146+
const value = Number(valueStr);
147+
if (isNaN(value)) {
148+
vscode.window.showErrorMessage("Invalid number");
149+
return;
150+
}
151+
stringFunc = (commandNameFunctionMap[commandName] as Function)(value);
152+
} else if (numberFunctionNames.includes(commandName)) {
153+
stringFunc = (str: string) =>
154+
(commandNameFunctionMap[commandName] as Function)(str, multiselectData);
155+
} else {
156+
stringFunc = commandNameFunctionMap[commandName] as StringFunction;
157+
}
158+
159+
for (const [index, selection] of editor.selections.entries()) {
160+
const text = editor.document.getText(selection);
161+
const textParts = text.split("\n");
162+
const replaced = textParts.map((part) => stringFunc(part)).join("\n");
163+
replacedSelections.push(replaced);
164+
selectionMap[index] = { selection, replaced };
165+
}
166+
167+
if (shouldApply) {
168+
await editor.edit((builder) => {
169+
Object.values(selectionMap).forEach(({ selection, replaced }) => {
170+
builder.replace(selection, replaced);
171+
});
172+
});
173+
174+
context.globalState.update("lastAction", commandName);
175+
}
176+
177+
return await Promise.resolve({ replacedSelections });
178+
};
179+
180+
export function activate(context: vscode.ExtensionContext) {
181+
context.globalState.setKeysForSync(["lastAction"]);
182+
183+
context.subscriptions.push(
184+
vscode.commands.registerCommand(
185+
"string-manipulation.repeatLastAction",
186+
() => {
187+
const lastAction = context.globalState.get<string>("lastAction");
188+
if (lastAction) {
189+
return stringFunction(lastAction, context);
190+
}
191+
}
192+
)
193+
);
194+
195+
Object.keys(commandNameFunctionMap).forEach((commandName) => {
196+
context.subscriptions.push(
197+
vscode.commands.registerCommand(
198+
`string-manipulation.${commandName}`,
199+
() => stringFunction(commandName, context)
200+
)
201+
);
202+
});
203+
}
204+
205+
export { commandNameFunctionMap };

0 commit comments

Comments
 (0)