Skip to content

Commit 1fc6771

Browse files
Initial effort to migrate to typescript
1 parent 19856bb commit 1fc6771

File tree

8 files changed

+210
-154
lines changed

8 files changed

+210
-154
lines changed

.github/workflows/publish-extension.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ jobs:
1313
steps:
1414
- name: Checkout to branch
1515
uses: actions/checkout@v4
16+
1617
- name: Setup node.js
1718
uses: actions/setup-node@v4
1819
with:
1920
node-version: 20
21+
2022
- name: "Bump version"
2123
uses: 'phips28/gh-action-bump-version@master'
2224
env:
@@ -36,8 +38,9 @@ jobs:
3638
APP_VERSION=`cat package.json | jq ".version" -M | sed 's/\"//g'`
3739
echo "AppVersion=$APP_VERSION" >> $GITHUB_OUTPUT
3840
echo "app version = v$APP_VERSION"
41+
3942
- name: Build VSIX package
40-
run: npm run build -- -o vscode-string-manipulation.v${{ steps.calculateVersion.outputs.AppVersion }}.vsix
43+
run: npm run package -- -o vscode-string-manipulation.v${{ steps.calculateVersion.outputs.AppVersion }}.vsix
4144

4245
- name: Publish extension package
4346
env:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ node_modules
22
*.vsix
33
.vscode-test
44
.DS_Storedist
5+
out/

extension.js

Lines changed: 0 additions & 140 deletions
This file was deleted.

package-lock.json

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

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,8 @@
333333
},
334334
"scripts": {
335335
"test": "node ./test/runTest.js",
336-
"build": "(rm -rf out || true) && mkdir out && cp package.json out && vsce package",
336+
"build": "tsc",
337+
"package": "(rm -rf out || true) && mkdir out && cp package.json out && vsce package",
337338
"vsce": "vsce"
338339
},
339340
"devDependencies": {
@@ -346,7 +347,7 @@
346347
"glob": "^7.1.5",
347348
"mocha": "^10.2.0",
348349
"sinon": "^9.2.4",
349-
"typescript": "^3.9.9",
350+
"typescript": "^3.9.10",
350351
"vsce": "^2.15.0",
351352
"vscode-test": "^1.6.1"
352353
},

src/extension.ts

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

0 commit comments

Comments
 (0)