Товарищи, проблема следующая: привык в своих проектах сортировать usingи (и не только) по длине строки. То есть код рода:
using System.Collections.Generic;
using System;
using System.Linq;
using System.IO;
Я обязательно переделаю в:
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
И только тогда успокоюсь)
Так вот. Я немного устал делать это вручную, так что хочу спросить: есть ли какое-либо легкое расширение (подозреваю, что ReSharper так умеет, но его я не рассматриваю) для Visual Studio 2017, способное выполнять сортировку выделенных строк по их длине?
Ответ
Собственно, никто мне так и не ответил, сам же я не нашел подходящего расширения. Но кто ж мне мешал написать свое? Сие я и сделал. Расширение теперь висит в Marketplace, а именно - здесь Оформлять сильно все это я, понятное дело, не стал, ибо делал сие только под себя, но мало ли кому-то когда-нибудь тоже пригодится такая мелочь, а ставить внушительные расширения хотят далеко не все.
Ну и давайте заодно уж рассмотрим пример создания такого простейшего расширения на поставленной задаче.
0) Для начала создадим VSIX Project (расширения для Visual Studio можно писать на Visual C++, Visual Basic, C#. Я это дело писал на C#). Если шаблон такого проекта отсутствует в списке доступных, установите соответствующий пункт с помощью Visual Studio Installer.
1) Добавим в проект элемент Custom Command и назовем его "CommandSort".
В появившийся класс "CommandSort.cs" добавим следующий код:
using System;
using System.Linq;
using Microsoft.VisualStudio.Shell;
using System.ComponentModel.Design;
namespace LineSorter
{
internal sealed class CommandSort
{
#region Var
private AsyncPackage Package { get; }
public int CommandId { get; } = 0x0100;
public static CommandSort Instance { get; private set; }
public static Guid CommandSet { get; } = new Guid("e9f69e2b-6313-4c2b-9765-1ddd6439d519");
#endregion
#region Init
private CommandSort(AsyncPackage Package, OleMenuCommandService CommandService)
{
this.Package = Package ?? throw new ArgumentNullException(nameof(Package));
CommandService = CommandService ?? throw new ArgumentNullException(nameof(CommandService));
CommandID menuCommandID = new CommandID(CommandSet, CommandId);
MenuCommand menuItem = new MenuCommand(Execute, menuCommandID);
CommandService.AddCommand(menuItem);
}
public static async System.Threading.Tasks.Task InitializeAsync(AsyncPackage Package)
{
ThreadHelper.ThrowIfNotOnUIThread();
Instance = new CommandSort(Package, await Package.GetServiceAsync((typeof(IMenuCommandService))) as OleMenuCommandService);
}
#endregion
#region Functions
private void Execute(object sender, EventArgs e)
{
ThreadHelper.ThrowIfNotOnUIThread();
TextSelection.GetSelection(Package).OrderBy(x => x.Length).ThenBy(x => x).ReplaceSelection();
}
#endregion
}
}
Инициализатор нашей команды, ее ID и GUID в рамках проекта будут установлены VS автоматически. По сути от нас требуется изменить лишь метод Execute
2) Для более удобной работы с выделенным текстом создадим класс "TextSelection" и в "TextSelection.cs" внесем таковой код:
using EnvDTE;
using System;
using System.Linq;
using System.Windows;
using System.Collections.Generic;
using Microsoft.VisualStudio.TextManager.Interop;
namespace LineSorter
{
public static class TextSelection
{
#region Var
public static IServiceProvider ServiceProvider { get; set; }
#endregion
#region Functions
///
', '
' }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim(new char[] { ' ', '\t' }));
}
public static void ReplaceSelection(this IEnumerable
", Selections));
dte.ExecuteCommand("Edit.Paste");
Clipboard.SetDataObject(obj);
}
#endregion
}
}
Логика кода, думаю, вполне ясна. Однако заострю внимание на методе замены выделенного текста на новую коллекцию (ReplaceSelection). Изначально код выглядел так:
DTE dte = ServiceProvider?.GetService(typeof(DTE)) as DTE;
if (dte is null) return;
EnvDTE.TextSelection selection = dte.ActiveDocument?.Selection as EnvDTE.TextSelection;
if (selection is null) return;
selection.Text = string.Join("
", Selections);
И все кажется вполне логичным: просто заменяем выделенный текст на новый. Во многих мануалах я как раз такой подход и видел. Однако предостерегаю от такого подхода к работе с выделенным текстом, так как работает это жутко медленно. Свойство EnvDTE.TextSelection.Text при установке зачем-то парсит весь вставляемый текст, разбивая его на смысловые единицы. Поясню: к примеру, мы имеем 2 таких using'a, которые мы хотим отсортировать:
using Microsoft.VisualStudio.Shell.Interop;
using Task = System.Threading.Tasks.Task;
Для нашего алгоритма важно лишь то, что это 2 строки. Однако указанное свойство разбивает все это дело на смысловой набор:
using, Microsoft, VisualStudio, Shell, Interop, using, Task, System, Threading, Tasks, Task
И мы имеем уже не 2 строки, а 11 отдельно обрабатываемых элементов. С ростом числа сортируемых строк число обрабатываемых смысловых единиц катастрофически растет, так что простенькое действие при таком подходе начинает занимать внушительный промежуток времени. Именно поэтому я воспользовался маленькой хитростью:
Я сохраняю текущее состояние буфера обмена
После этого загоняю в него объединенную коллекцию наших строк
А теперь уже запускаю функцию вставки из самой VS (как бы Ctrl +
V). Так что текст моментально вставляется на место выделенного,
после чего форматируется самой средой
После сего действа возвращаю буферу обмена прошлое состояние
Честно - не знаю, насколько хорошо это решение, однако оно уж точно быстрее стандартного пути. Если будут идеи более правильной реализации - буду рад выслушать!
3) Что ж. Разобрались, собственно, с кодом. Осталось навести порядок в конфиге (CommandSortPackage.vsct):
Таким образом мы добавляем кнопку с указанным текстом и ссылающуюся на наше действие в контекстное меню VS, а также связываем с ним горячие клавиши:
(Ctrl + E) + (Ctrl + L)
4) Вот и все, наше простенькое расширение готово. Вы можете отдебажить его с помощью экспериментальной версии VS, или же просто собрать его, после чего среди уймы пакетов Вы найдете заветный файлик с расширением *.vsix, при помощи которого Вы можете либо сразу установить расширение в свою студию, либо же даже опубликовать его)
А на сием - все) Спасибо за внимание)
Комментариев нет:
Отправить комментарий