# vs插件等号对齐
**Repository Path**: JJbox/JoinBoxAlignPlug
## Basic Information
- **Project Name**: vs插件等号对齐
- **Description**: 用于vs保存时候进行等号对齐,阿惊的学习笔记,内容不一定正确.
- **Primary Language**: C#
- **License**: MIT
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 2
- **Created**: 2021-06-08
- **Last Updated**: 2023-04-08
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 本插件功能
1. 保存事件
- [x] 发送格式化
- [x] 删除行尾空格
- [x] 对齐等号
- [x] 对齐注释
2. 制作工具条
- [x] 启用或者禁用插件
3. 模仿vscode的
- [ ] alt+鼠标右键到行尾
4. 后缀补全
- [x] 利用\`if将本行代码加入到if(括号内),同理\`foreach也可以...其他就没做了
因为不知道vs的语义切割是怎么做的,看教程是用CodeElement,我又急迫的想做出来,所以用的字符串切割.甚至没有用一条正则..
我是判断**大括号内连续**,连续条件是去掉注释和空行之后,括号上下行有=号.
**枚举类型**豁免,全部为连续.
如果是
{
int a=1;
list.clear();
}
这样就不为连续.也可以给cpp用.
# vs2019安装拓展程序开发环境



# 创建VSIX项目


*Package.cs 文件是程序入口,相当于Main:
(在VS2017+是继承自AsyncPackage,采用异步方式加载,2010中继承自Package,同步加载方式,AsyncPackage也继承自Package类)
继承自AsyncPackage的Package的核心类,由于是根据Visual Studio启动的外部插件,加载该插件时会实例化该类;
## 配置.vsixmanifest文件
1. 打开source.extension.vsixmanifest文件, 填写基本信息

2. 选择Install Targets, 配置插件适用的VS版本

配置插件适用的VS版本
> VS版本:
> VS2019=>16.0
> VS2017=>15.0
> VS2015=>14.0
> VS2013=>12.0
部分摘录自 https://www.jianshu.com/p/ac33559cd8c0
# 新建Editor Viewport Adornment

新建完成之后就可以进行F5调试了,打开了一个文档之后,你会发现右上角有个实验案例的小框框.
解释每个内容需要阅读此文: https://www.cnblogs.com/stg609/p/3711443.html
他主要讲解了,Editor几种工程的不同,
还有:IWpfTextViewCreationListener 接口,当出现文档窗口(写代码的窗口)时候就会激发它下面的TextViewCreated函数.
而其中修改的核心内容,背后是调用了vs的语言服务器的代码规则:
```c#
// 在激活文档中获取选中的文本
var Selection = (TextSelection)_dte.ActiveDocument.Selection;
// 获取代码块也就是大括号{}
CodeFunction func = Selection.ActivePoint.CodeElement[vsCMElement.vsCMElementFunction] as CodeFunction;
if (func != null)
{
if (true)//然后移动光标到大括号前后
{
Selection.MoveToPoint(func.StartPoint);
}
else
{
Selection.MoveToPoint(func.EndPoint);
}
}
```
阅读完之后,你应该知道操作Visual Studio是靠DTE或者DTE2(比较新)
```c#
public TskComment(IWpfTextView view, DTE2 dte) //在包接口获取DTE对象之后,传入其他对象.
```
而有写地方是异步处理的,因此有两种方式掌握:
同步方法
```c#
//成员处
[Import]
internal SVsServiceProvider ServiceProvider = null;
//函数内
var _dTE = ServiceProvider.GetService(typeof(DTE)) as DTE;
```
异步方法
```c#
//函数内
//这样DTE获取的东西都不会有警告了
//切换到主线程异步 https://github.com/Microsoft/vs-threading/blob/main/doc/analyzers/VSTHRD010.md
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var _dTE = await GetServiceAsync(typeof(DTE)) as DTE2;//初始化获取DTE对象,是用AsyncPackage拿的.异步
// package获取
// _dTE = await package.GetServiceAsync(typeof(DTE)) as DTE2; // 命令内获取dte方法.异步
```
## 新建命令

文件名是 Command1.cs
## 通过新建命令来添加.vsct失败
JoinBoxAlignPlugPackage.cs内,如果有这句的话,就会发生这样的事情:
```c#
[ProvideMenuResource("Menus.ctmenu", 1)]
```
而添加过一次命令的话,这句会自动加入到此处,是vs用来防止多次覆盖.vsct用的.
## 设置快捷键1_组合键
因为文件名的原因,它的命令按钮id自动设置为 Command1Id, 它很重要 ,因为快捷键绑定需要指定.
打开.vsct文件
```xml
..省略其他项..
```
然后就可以进入调试看看效果了...

微软参考 https://docs.microsoft.com/zh-cn/visualstudio/extensibility/binding-keyboard-shortcuts-to-menu-items?view=vs-2019
其他参考 https://www.cnblogs.com/forevertime/p/9358994.html
## 设置快捷键2_拦截自带的ctrl+s
上面的方法并不适用在冲突的快捷键上面使用,
所以我的想法是在TextViewCreated函数中挂一个**钩子拦截**ctrl+s.
```c#
internal sealed class TskCommentTextViewCreationListener : IWpfTextViewCreationListener //文档窗口侦听器
{
//省略展示中间部分
public void TextViewCreated(IWpfTextView textView)
{
ThreadHelper.ThrowIfNotOnUIThread();
_dTE = ServiceProvider.GetService(typeof(DTE)) as DTE2; //<-- 获取DTE对象
// 因为每次打开文档就会执行 TextViewCreated 函数,所以需要设置成单例,用钩子拦截
if (_keyboardHook == null)
{
_keyboardHook = new KeyboardHook();
_keyboardHook.KeyDownEvent += KeyboardHook_KeyDownEvent;
}
new TskComment(textView, _dTE); //<-- 把dte传给 view
}
}
```
不过此方法会拦截并触发系统所有的ctrl+s,若通过当前鼠标获取句柄Win32API.GetForegroundWindow,则由于vs是WPF做的,难以判断文档窗口句柄,
尤其是文档窗口被拖拽出去之后,它会新建一个窗口出现,并且不是vsDTE2.Application.MainWindow的子窗口.
因此需要利用**文档保存事件**.
## 文档保存事件
参考 https://stackoverflow.com/questions/41620241/can-i-detect-document-saved-not-changed-with-visual-studio-workspace-in-a-vsix
```c#
internal sealed class EditorViewTextViewCreationListener : IWpfTextViewCreationListener
{
///////省略中间部分
[Import]
internal SVsServiceProvider vsService = null; //<-- 通过这句代码,同步获取 Visual Studio 的产物
public static DTE2 vsDTE2 { get; set; }
public Events2 _events2 { get; private set; }
public DocumentEvents _documentEvents { get; private set; }
public void TextViewCreated(IWpfTextView textView)
{
ThreadHelper.ThrowIfNotOnUIThread();
vsDTE2 = vsService.GetService(typeof(DTE)) as DTE2; //<-- 获取DTE对象
//文档保存的事件:
//成员不能移除,否则GC回收
this._events2 = (Events2)vsDTE2.Events;
this._documentEvents = this._events2.DocumentEvents;
this._documentEvents.DocumentSaved += DocumentEvents_DocumentSaved;
}
bool _Save = true;
private void DocumentEvents_DocumentSaved(Document Document)
{
ThreadHelper.ThrowIfNotOnUIThread();
// 防止重复触发保存事件
if (!_Save)
return;
JoinBoxCommand.CmdExecute(vsDTE2, Document);
if (_Save && !Document.Saved)
{
_Save = false;
Document.Save();//那这个时候怎么保存呢?
_Save = true;
}
}
}
```
# DTE使用
## 格式化代码
方法1:编辑器
```c#
_dTE.ExecuteCommand("Edit.FormatDocument");// 发送命令:格式化代码,绕开选择文本造成滚动条改变(好用)
```
方法2:编辑器
```c#
var doc = _dTE?.ActiveDocument;
var selection = (TextSelection)doc.Selection;
// 获取当前文档所有文本
selection.SelectAll();//选择会跳动滚动条
selection.SmartFormat();//格式化
```
方法3:缓冲区
```c#
var doc = _dTE?.ActiveDocument;
var objTD = (TextDocument)doc.Object();
objTD.Selection.SelectAll();//选择会跳动滚动条
objTD.Selection.SmartFormat();// 格式化
```
## 获取编辑器文本
获取当前文档所有文本,但是要注意此方法会导致上下滚动条移动..不想的话要用 [CreateEditPoint](#CreateEditPoint优)
```c#
var doc = _dTE?.ActiveDocument;
var selection = (TextSelection)doc.Selection;// 文本选择器
selection.SelectAll();
var str1 = selection.Text;
```
## CreateEditPoint优
微软帮助 https://docs.microsoft.com/zh-cn/dotnet/api/envdte.editpoint?view=visualstudiosdk-2019#properties
```c#
var doc = _dTE?.ActiveDocument;
var objTD = (TextDocument)doc.Object();
var objEP = objTD.StartPoint.CreateEditPoint();
var str1 = objEP.GetText(objTD.EndPoint);//获取所有文本
string contents = ChangTextContents(str1, out int row);//修改文本
objEP.ReplaceText(objTD.EndPoint, contents, (int)vsEPReplaceTextOptions.vsEPReplaceTextKeepMarkers);//编辑点替换方式
```
## 替换文本多种方法
```c#
objEP.ReplaceText(objTD.EndPoint, contents, (int)vsEPReplaceTextOptions.vsEPReplaceTextKeepMarkers);//编辑点替换方式
//doc.ReplaceText(str1, contents);//可
//selection2.ReplaceText(str1, contents);//可
//selection.ReplacePattern(str1, contents);//可
//selection.DestructiveInsert(contents);//单纯插入,不可
//以下不好
//selection.Text = contents;//这样会卡死
//selection.Insert(contents, 4);//设置了会选中所有文本..
```
## 移动光标
```c#
var doc = _dTE?.ActiveDocument;
var selection = (TextSelection)doc.Selection;// 文本选择器
selection.StartOfLine(vsStartOfLineOptions.vsStartOfLineOptionsFirstColumn);//移动到行头
selection.EndOfLine();//移动到行尾
selection.OutlineSection();//上一行
selection.LineDown();//设置光标移动下一行
//记录行和列
var lStartLine = selection.ActivePoint.Line;
var lStartColumn = selection.ActivePoint.LineCharOffset;
selection.MoveToLineAndOffset(lStartLine, lStartColumn);//移动到指定行和列
```
# 界面
## 菜单栏
在.vsct文件中是利用guid="guidSHLMainMenu"此方式加入vs的菜单中...
```xml