1
0
Code Issues Pull Requests Packages Projects Releases Wiki Activity GitHub Gitee

支持通过快捷键滚动转换 (同时支持 Alt 鼠标选择的多行选区转换)

This commit is contained in:
程序员小墨 2024-04-09 00:41:28 +08:00
parent 4ef50ea556
commit 586c17d9ef
8 changed files with 299 additions and 51 deletions

View File

@ -17,14 +17,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
### Changed ### Changed
### Removed ### Removed
--> -->
## 1.0.8
### Added
- Supports scrolling conversion via shortcut keys `Ctrl + Alt + [` and `Ctrl + Alt + ]` (simultaneously supports multi-line selection conversion) 支持通过快捷键滚动转换 (同时支持多行选区转换)
## 1.0.7 ## 1.0.7

View File

@ -4,7 +4,7 @@
"displayName": "Variable Conversion", "displayName": "Variable Conversion",
"description": "一个强大的变量名转换插件,支持右键菜单、快捷键、底栏等多种方式使用,支持小驼峰、大驼峰(帕斯卡)、下划线(蛇形)、中划线(连字符/脊柱式)、空格分隔、全小写、全大写等常用命名方式(及组合)转换。 \nA powerful variable naming conversion extension. You can use it through the editer menu, shortcut keys and bottom bar. Support camel, pascal, snake, kebab(spinal), space, lower, upper case, and more.", "description": "一个强大的变量名转换插件,支持右键菜单、快捷键、底栏等多种方式使用,支持小驼峰、大驼峰(帕斯卡)、下划线(蛇形)、中划线(连字符/脊柱式)、空格分隔、全小写、全大写等常用命名方式(及组合)转换。 \nA powerful variable naming conversion extension. You can use it through the editer menu, shortcut keys and bottom bar. Support camel, pascal, snake, kebab(spinal), space, lower, upper case, and more.",
// 版本号 // 版本号
"version": "1.0.7", "version": "1.0.8",
// logo // logo
"icon": "image/logo.png", "icon": "image/logo.png",
"publisher": "coder-xiaomo", "publisher": "coder-xiaomo",
@ -37,7 +37,45 @@
"onTextSelected" "onTextSelected"
], ],
"contributes": { "contributes": {
// docs: https://code.visualstudio.com/docs/getstarted/keybindings#_accepted-keys
"keybindings": [
// 绑定快捷键
{
"command": "variable-conversion.convertCase",
"key": "shift+alt+t",
"when": "editorTextFocus"
},
// 滚动转换 上一个
{
"command": "variable-conversion.cyclicConvertCase.previous",
"key": "ctrl+alt+[",
"args": {
"arrowKey": "["
},
"when": "editorTextFocus"
},
// 滚动转换 下一个
{
"command": "variable-conversion.cyclicConvertCase.next",
"key": "ctrl+alt+]",
"args": {
"arrowKey": "]"
},
"when": "editorTextFocus"
}
],
"commands": [ "commands": [
/**
* 滚动转换 可以不添加
*/
// {
// "command": "variable-conversion.cyclicConvertCase.previous",
// "title": "字符串转换(上一个)"
// },
// {
// "command": "variable-conversion.cyclicConvertCase.next",
// "title": "字符串转换(下一个)"
// },
/** /**
* 右键菜单 * 右键菜单
*/ */
@ -149,15 +187,6 @@
// "enablement": "false" // "enablement": "false"
// } // }
], ],
// docs: https://code.visualstudio.com/docs/getstarted/keybindings#_accepted-keys
"keybindings": [
// 绑定快捷键
{
"command": "variable-conversion.convertCase",
"key": "shift+alt+t",
"when": "editorTextFocus"
}
],
// docs: https://code.visualstudio.com/api/references/contribution-points#contributes.menus // docs: https://code.visualstudio.com/api/references/contribution-points#contributes.menus
"menus": { "menus": {
// 编辑器右键菜单 // 编辑器右键菜单

View File

@ -2,7 +2,7 @@
"name": "variable-conversion", "name": "variable-conversion",
"displayName": "Variable Conversion", "displayName": "Variable Conversion",
"description": "一个强大的变量名转换插件,支持右键菜单、快捷键、底栏等多种方式使用,支持小驼峰、大驼峰(帕斯卡)、下划线(蛇形)、中划线(连字符/脊柱式)、空格分隔、全小写、全大写等常用命名方式(及组合)转换。 \nA powerful variable naming conversion extension. You can use it through the editer menu, shortcut keys and bottom bar. Support camel, pascal, snake, kebab(spinal), space, lower, upper case, and more.", "description": "一个强大的变量名转换插件,支持右键菜单、快捷键、底栏等多种方式使用,支持小驼峰、大驼峰(帕斯卡)、下划线(蛇形)、中划线(连字符/脊柱式)、空格分隔、全小写、全大写等常用命名方式(及组合)转换。 \nA powerful variable naming conversion extension. You can use it through the editer menu, shortcut keys and bottom bar. Support camel, pascal, snake, kebab(spinal), space, lower, upper case, and more.",
"version": "1.0.7", "version": "1.0.8",
"icon": "image/logo.png", "icon": "image/logo.png",
"publisher": "coder-xiaomo", "publisher": "coder-xiaomo",
"engines": { "engines": {
@ -31,6 +31,29 @@
"onTextSelected" "onTextSelected"
], ],
"contributes": { "contributes": {
"keybindings": [
{
"command": "variable-conversion.convertCase",
"key": "shift+alt+t",
"when": "editorTextFocus"
},
{
"command": "variable-conversion.cyclicConvertCase.previous",
"key": "ctrl+alt+[",
"args": {
"arrowKey": "["
},
"when": "editorTextFocus"
},
{
"command": "variable-conversion.cyclicConvertCase.next",
"key": "ctrl+alt+]",
"args": {
"arrowKey": "]"
},
"when": "editorTextFocus"
}
],
"commands": [ "commands": [
{ {
"command": "variable-conversion.convertCase", "command": "variable-conversion.convertCase",
@ -101,13 +124,6 @@
"title": "全大写 (Upper Case) [ FOOBAR ]" "title": "全大写 (Upper Case) [ FOOBAR ]"
} }
], ],
"keybindings": [
{
"command": "variable-conversion.convertCase",
"key": "shift+alt+t",
"when": "editorTextFocus"
}
],
"menus": { "menus": {
"editor/context": [ "editor/context": [
{ {

View File

@ -5,6 +5,8 @@ import * as vscode from 'vscode';
let statusBar: vscode.StatusBarItem; let statusBar: vscode.StatusBarItem;
/** /**
*
*
* @since 2024-04-07 * @since 2024-04-07
*/ */
export function createStatusBarItem() { export function createStatusBarItem() {
@ -16,6 +18,8 @@ export function createStatusBarItem() {
} }
/** /**
*
*
* @since 2024-04-07 * @since 2024-04-07
*/ */
export function updateStatusBarItemVisable(selectTextLength: number) { export function updateStatusBarItemVisable(selectTextLength: number) {

View File

@ -5,56 +5,80 @@ import handleEditorReplace from './extension-handler/editor-submenu-handler';
import { handleQuickPick } from './extension-handler/quick-pick-handler'; import { handleQuickPick } from './extension-handler/quick-pick-handler';
import { commands } from './type-definition/SupportCaseType'; import { commands } from './type-definition/SupportCaseType';
import { createStatusBarItem, updateStatusBarItemVisable } from './extension-handler/status-bar-handler'; import { createStatusBarItem, updateStatusBarItemVisable } from './extension-handler/status-bar-handler';
import * as CyclicConversion from './main-code/cyclic-conversion';
import { EOL } from './type-definition/EOLType';
// This method is called when your extension is activated // This method is called when your extension is activated
// Your extension is activated the very first time the command is executed // Your extension is activated the very first time the command is executed
export function activate(context: vscode.ExtensionContext) { export function activate(context: vscode.ExtensionContext) {
try {
// 获取当前插件的扩展对象
const currentExtension = vscode.extensions.getExtension('coder-xiaomo.variable-conversion');
if (currentExtension) {
// 获取版本号
const version = currentExtension.packageJSON.version;
console.log('[Variable Conversion] current version:', version);
}
} catch (err) {
console.log('get current extension failed', err);
}
// // Use the console to output diagnostic information (console.log) and errors (console.error) // 用于记录当前选中的文本的长度
// // This line of code will only be executed once when your extension is activated
// console.log('Congratulations, your extension "variable-conversion" is now active!');
// // The command has been defined in the package.json file
// // Now provide the implementation of the command with registerCommand
// // The commandId parameter must match the command field in package.json
// let disposable = vscode.commands.registerCommand('variable-conversion.helloWorld', () => {
// // The code you place here will be executed every time your command is executed
// // Display a message box to the user
// vscode.window.showInformationMessage('Hello World from variable-conversion!');
// });
let selectTextLength = 0; let selectTextLength = 0;
// 选中文本改变时触发
const onTextEditorSelectionChangeCallback = (textEditor: vscode.TextEditor, selections: readonly vscode.Selection[]) => {
// 获取选中的文本
const text: string = textEditor.document.getText(selections[0]);
selectTextLength = text.length;
// 获取选中的文本块
const textList: string[] = [];
for (const selection of selections) {
const text = textEditor.document.getText(selection);
textList.push(text);
}
// 更新 _textSelectionLength (用于判断是否展示右键菜单)
vscode.commands.executeCommand('setContext', '_textSelectionLength', selectTextLength);
// 判断是否展示状态栏按钮
updateStatusBarItemVisable(selectTextLength);
// 滚动转换:记录当前选中内容,并且进行转换
let eol: EOL = textEditor.document.eol === vscode.EndOfLine.CRLF ? '\r\n' : '\n';
CyclicConversion.onUserSelectionUpdated(selections, textList, eol);
};
// 创建状态栏按钮
createStatusBarItem(); createStatusBarItem();
/**
* Tab
*/
vscode.window.onDidChangeActiveTextEditor(event => { vscode.window.onDidChangeActiveTextEditor(event => {
console.log('onDidChangeActiveTextEditor', event);
// 判断是否展示状态栏按钮
updateStatusBarItemVisable(selectTextLength); updateStatusBarItemVisable(selectTextLength);
}); });
// 用于判断是否展示右键菜单 /**
*
*/
vscode.window.onDidChangeTextEditorSelection(event => { vscode.window.onDidChangeTextEditorSelection(event => {
const text = event.textEditor.document.getText(event.selections[0]); console.log('光标选中位置改变 onDidChangeTextEditorSelection', event);
// console.log('text.length', text.length); // 执行 Callback
vscode.commands.executeCommand('setContext', '_textSelectionLength', text.length); onTextEditorSelectionChangeCallback(event.textEditor, event.selections);
selectTextLength = text.length;
updateStatusBarItemVisable(selectTextLength);
}); });
// 初始(VSCode 插件初始化)时也判断一次 (考虑上次关闭 VSCode 有选区,重新打开后 VSCode 回复选区但用户未重新切换选区的场景) // 初始(VSCode 插件初始化)时也判断一次 (考虑上次关闭 VSCode 有选区,重新打开后 VSCode 回复选区但用户未重新切换选区的场景)
let editor = vscode.window.activeTextEditor; let editor = vscode.window.activeTextEditor;
if (editor) { if (editor) {
let document = editor.document; // VSCode 启动时打开的 Tab 不是编辑器 Tab, 执行 Callback (如果不是则跳过)
let selection = editor.selection; onTextEditorSelectionChangeCallback(editor, editor.selections);
// 获取选中的文本
let text = document.getText(selection);
vscode.commands.executeCommand('setContext', '_textSelectionLength', text.length);
selectTextLength = text.length;
updateStatusBarItemVisable(selectTextLength);
} else {
// vscode.window.showInformationMessage('editor is undefined');
console.log('editor is undefined');
} }
// 逐一注册右键菜单-子菜单项 command
for (const { command, targetCase } of commands) { for (const { command, targetCase } of commands) {
let disposable = vscode.commands.registerCommand(command, () => { let disposable = vscode.commands.registerCommand(command, () => {
handleEditorReplace(targetCase); handleEditorReplace(targetCase);
@ -62,8 +86,21 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(disposable); context.subscriptions.push(disposable);
} }
// 注册字符串转换 command 状态栏/快捷键/右键[字符串转换]菜单均有用到
let convertCaseDisposable = vscode.commands.registerCommand('variable-conversion.convertCase', handleQuickPick); let convertCaseDisposable = vscode.commands.registerCommand('variable-conversion.convertCase', handleQuickPick);
context.subscriptions.push(convertCaseDisposable); context.subscriptions.push(convertCaseDisposable);
// 注册滚动转换 command
let disposableLoopConversionPrev = vscode.commands.registerCommand('variable-conversion.cyclicConvertCase.previous', ({ arrowKey }) => {
console.log('variable-conversion.convertCase', arrowKey);
CyclicConversion.previousOne();
});
context.subscriptions.push(disposableLoopConversionPrev);
let disposableLoopConversionNext = vscode.commands.registerCommand('variable-conversion.cyclicConvertCase.next', ({ arrowKey }) => {
console.log('variable-conversion.convertCase', arrowKey);
CyclicConversion.nextOne();
});
context.subscriptions.push(disposableLoopConversionNext);
} }
// This method is called when your extension is deactivated // This method is called when your extension is deactivated

View File

@ -0,0 +1,104 @@
import * as vscode from 'vscode';
import { EOL } from "../type-definition/EOLType";
import { cyclicConvertCaseOrder } from "../type-definition/SupportCaseType";
import { caseConversion } from "./conversion";
import { isStringArrayEqual, stringListArrayDuplicateRemoval } from './utils';
interface UserSelection {
currentEol: EOL
currentSelections?: readonly vscode.Selection[]
currentSelectionsText: string[]
currentIndex: number
isConverted: boolean
conversionsTarget: Array<string[]>
lastConvertedSelectionsText: string[] // 按快捷键后转换的值(如果下次触发 onUserSelectionUpdated 后传入值是这个,那么跳过,避免丢失当前滚动转换记录)
}
const userSelection: UserSelection = {
currentEol: '\n',
// currentSelections: undefined,
currentSelectionsText: [],
currentIndex: 0,
isConverted: false,
conversionsTarget: [], // 转换后去重 剩余转换目标
lastConvertedSelectionsText: [],
};
export function onUserSelectionUpdated(selections: readonly vscode.Selection[], textList: string[], eol: EOL): void {
if (textList.length !== 0 && isStringArrayEqual(textList, userSelection.lastConvertedSelectionsText)) {
console.log('skip onUserSelectionUpdated');
return;
}
console.log('onUserSelectionUpdated', textList, userSelection.lastConvertedSelectionsText);
userSelection.currentEol = eol;
userSelection.currentSelections = selections;
userSelection.currentSelectionsText = textList;
userSelection.currentIndex = 0;
userSelection.isConverted = false;
userSelection.conversionsTarget = [];
userSelection.lastConvertedSelectionsText = textList;
}
export function previousOne() {
lazyConvert();
const length = userSelection.conversionsTarget.length;
const oldIndex = userSelection.currentIndex;
const newIndex = oldIndex === 0 ? (length - 1) : (oldIndex - 1);
userSelection.currentIndex = newIndex;
console.log('previousOne oldIndex', oldIndex, 'newIndex', newIndex);
replaceTextEditorSelectedText();
}
export function nextOne() {
lazyConvert();
const length = userSelection.conversionsTarget.length;
const oldIndex = userSelection.currentIndex;
const newIndex = oldIndex >= length - 1 ? 0 : (oldIndex + 1);
userSelection.currentIndex = newIndex;
console.log('nextOne oldIndex', oldIndex, 'newIndex', newIndex);
replaceTextEditorSelectedText();
}
function lazyConvert() {
if (userSelection.isConverted) {
return;
}
const textList = userSelection.currentSelectionsText;
const eol = userSelection.currentEol;
const conversionsTarget: Array<string[]> = [];
for (const cyclicConvertCase of cyclicConvertCaseOrder) {
// 每一个类型
const conversionsTargetItem: string[] = [];
for (const line of textList) {
// 选中区块的每一行
const conversionResult: string = caseConversion(cyclicConvertCase, line, eol);
conversionsTargetItem.push(conversionResult);
}
conversionsTarget.push(conversionsTargetItem);
}
// 按数组去重
const noDuplicate = stringListArrayDuplicateRemoval(conversionsTarget);
console.log('noDuplicate', noDuplicate);
userSelection.conversionsTarget = conversionsTarget;
userSelection.isConverted = true;
}
function replaceTextEditorSelectedText() {
let editor = vscode.window.activeTextEditor;
if (editor) {
const selections = userSelection.currentSelections || [];
const textList = userSelection.conversionsTarget[userSelection.currentIndex];
console.log('selections', selections, 'textList', textList);
editor.edit(editBuilder => {
for (let i = 0; i < selections.length; i++) {
const selection = selections[i];
const converted = textList[i];
editBuilder.replace(selection, converted);
}
});
userSelection.lastConvertedSelectionsText = textList;
}
}

27
src/main-code/utils.ts Normal file
View File

@ -0,0 +1,27 @@
export function isStringArrayEqual(array1: string[], array2: string[]) {
if (array1.length !== array2.length) {
return false;
}
for (let index = 0; index < array1.length; index++) {
const element1 = array1[index];
const element2 = array2[index];
if (element1 !== element2) {
return false;
}
}
return true;
}
export function stringListArrayDuplicateRemoval(stringArr: Array<string[]>): Array<string[]> {
const tempArr: Array<string> = [];
const newArr: Array<string[]> = [];
for (let index = 0; index < stringArr.length; index++) {
const element = stringArr[index];
const elementStr = JSON.stringify(element);
if (!tempArr.includes(elementStr)) {
newArr.push(element);
tempArr.push(elementStr);
}
}
return newArr;
}

View File

@ -359,3 +359,31 @@ export const quickPickSupportCases = [
keyword: keyword.upper, keyword: keyword.upper,
}, },
]; ];
/**
*
* @since 2024-04-08
*/
export const cyclicConvertCaseOrder = [
SupportCase.CAMEL_CASE,
SupportCase.PASCAL_CASE,
SupportCase.SNAKE_CASE,
SupportCase.KEBAB_CASE,
SupportCase.SPACE_CASE,
SupportCase.SNAKE_UPPER_CASE,
SupportCase.KEBAB_UPPER_CASE,
SupportCase.SPACE_UPPER_CASE,
SupportCase.SNAKE_PASCAL_CASE,
SupportCase.KEBAB_PASCAL_CASE,
SupportCase.SPACE_PASCAL_CASE,
SupportCase.SNAKE_CAMEL_CASE,
SupportCase.KEBAB_CAMEL_CASE,
SupportCase.SPACE_CAMEL_CASE,
SupportCase.LOWER_CASE,
SupportCase.UPPER_CASE,
];