├── resources
├── ue4_smarter_macro_indenting_off.gif
└── ue4_smarter_macro_indenting_on.gif
├── LICENSE
├── README.md
├── ue4_smarter_macro_indenting_vs2013-2015.vcmd
└── ue4_smarter_macro_indenting_vs2017-2019.vcmd
/resources/ue4_smarter_macro_indenting_off.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackalyze/ue4-vs-extensions/HEAD/resources/ue4_smarter_macro_indenting_off.gif
--------------------------------------------------------------------------------
/resources/ue4_smarter_macro_indenting_on.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hackalyze/ue4-vs-extensions/HEAD/resources/ue4_smarter_macro_indenting_on.gif
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 hackalyze
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### UE4 Smarter Macro Indenting
2 | This extension was designed to fix the unnecessary and annoying "smart" indenting that Visual Studio likes to do around various UE4 macros. It attempts to fix these indents when your current line proceeds any of the following UE4 macros:
3 | * UPROPERTY
4 | * UFUNCTION
5 | * GENERATED_BODY
6 | * GENERATED_UCLASS_BODY
7 | * GENERATED_USTRUCT_BODY
8 | * GENERATED_UINTERFACE_BODY
9 | * GENERATED_IINTERFACE_BODY
10 |
11 | ### Demo
12 | | Before | After |
13 | | --- | --- |
14 | |  |  |
15 |
16 | ### Installation
17 |
18 | 1. Download and install the Visual Commander extension for Visual Studio from here: https://vlasovstudio.com/visual-commander/
19 | 2. Go to VCmd -> Import and import the ue4_smarter_macro_indenting*.vcmd that corresponds to your version of visual studio. For visual studio 2017/2019+, use ue4_smarter_macro_indenting_vs2017-2019.vcmd, for earlier versions use ue4_smarter_macro_indenting_vs2013-2015.vcmd.
20 | 3. Go to VCmd -> Extensions to open the Extensions panel
21 | 4. Check the "Enabled" checkbox for the "UE4 Fix Indent" extension.
22 | 5. Make sure Indenting is set to "Smart" in Tools -> Options -> Text Editor -> C/C++ -> Tabs.
23 |
24 | > If "VCmd" doesn't appear on your Visual Studio toolbar after installing Visual Commander, you may need to restart Visual Studio.
25 |
--------------------------------------------------------------------------------
/ue4_smarter_macro_indenting_vs2013-2015.vcmd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 1
7 | UE4 Smarter Macro Indenting
8 |
9 | using EnvDTE;
10 | using EnvDTE80;
11 | using Microsoft.VisualStudio;
12 | using System;
13 | using System.Text.RegularExpressions;
14 | using System.Windows.Forms;
15 |
16 | public class E : VisualCommanderExt.IExtension
17 | {
18 | private EnvDTE80.DTE2 _DTE;
19 | private EnvDTE80.TextDocumentKeyPressEvents _textDocumentKeyPressEvents;
20 | private static readonly string MACRO_REGEX = @"^(?<leading_whitespace>[\s]*)(UPROPERTY|UFUNCTION|GENERATED_(USTRUCT_|UCLASS_|(U|I)INTERFACE_)?BODY)\(.*";
21 | private static readonly string TEXT_WITH_WS_REGEX = @"(?<leading_whitespace>[\s]*)\S+";
22 |
23 | private CommandEvents _pasteEvent;
24 | private string _beforeText;
25 | private int _numSelectedLines;
26 |
27 | public void SetSite(DTE2 DTE, Microsoft.VisualStudio.Shell.Package package) {
28 | _DTE = DTE;
29 | EnvDTE80.Events2 events2 = (EnvDTE80.Events2)DTE.Events;
30 | _textDocumentKeyPressEvents = events2.get_TextDocumentKeyPressEvents(null);
31 | _textDocumentKeyPressEvents.AfterKeyPress += AfterKeyPress;
32 |
33 | var pasteGuid = typeof(VSConstants.VSStd97CmdID).GUID.ToString("B");
34 | var pasteID = (int)VSConstants.VSStd97CmdID.Paste;
35 | _pasteEvent = _DTE.Events.CommandEvents[pasteGuid, pasteID];
36 | _pasteEvent.BeforeExecute += BeforePaste;
37 | _pasteEvent.AfterExecute += AfterPaste;
38 | }
39 |
40 | public void Close() {
41 | _textDocumentKeyPressEvents.AfterKeyPress -= AfterKeyPress;
42 | _pasteEvent.AfterExecute -= AfterPaste;
43 | _pasteEvent.BeforeExecute -= BeforePaste;
44 | }
45 |
46 | private string GetActiveDocumentText() {
47 | TextDocument doc = (TextDocument)(_DTE.ActiveDocument.Object("TextDocument"));
48 | var editPoint = doc.StartPoint.CreateEditPoint();
49 | return editPoint.GetText(doc.EndPoint);
50 | }
51 |
52 | private void BeforePaste(string Guid, int ID, Object CustomIn, Object CustomOut, ref bool CancelDefault) {
53 | _beforeText = GetActiveDocumentText();
54 | TextSelection sel = (TextSelection)_DTE.ActiveDocument.Selection;
55 | _numSelectedLines = sel.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None).Length;
56 | }
57 |
58 | private void AfterPaste(string Guid, int ID, Object CustomIn, Object CustomOut) {
59 | string afterText = GetActiveDocumentText();
60 | string[] beforeLines = _beforeText.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
61 | string[] afterLines = afterText.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
62 | int pasteLength = (afterLines.Length - beforeLines.Length) + _numSelectedLines;
63 | TextSelection sel = (TextSelection)_DTE.ActiveDocument.Selection;
64 | var editPoint = sel.ActivePoint.CreateEditPoint();
65 | bool underMacro = false;
66 | string lastWhiteSpace = "";
67 | bool openedUndoContext = false;
68 | int startPasteLine = sel.ActivePoint.Line - pasteLength;
69 | int macroLineNum = startPasteLine + 1;
70 | // Search up the document from the start of our paste until we find a line with text on it
71 | // so we can then determine if this line contains a macro we care about
72 | while (--macroLineNum >= 1) {
73 | string macroLine = editPoint.GetLines(macroLineNum, macroLineNum + 1);
74 | if (!Regex.IsMatch(macroLine, @"\S+")) {
75 | continue;
76 | }
77 | var macroMatch = Regex.Match(macroLine, MACRO_REGEX);
78 | if (macroMatch.Success) {
79 | underMacro = true;
80 | lastWhiteSpace = macroMatch.Groups["leading_whitespace"].ToString();
81 | }
82 | break;
83 | }
84 |
85 | if (!_DTE.UndoContext.IsOpen) {
86 | openedUndoContext = true;
87 | // Open the UndoContext so all of our changes can be undone with a single undo
88 | _DTE.UndoContext.Open("FixUE4MacroIndents", false);
89 | }
90 | try {
91 | for (int onLine = startPasteLine; onLine < afterLines.Length; onLine++) {
92 | var line = afterLines[onLine];
93 | // We only need to potentially fix the newly pasted lines
94 | if (onLine >= startPasteLine + pasteLength) {
95 | sel.GotoLine(onLine, false);
96 | sel.MoveToPoint(editPoint);
97 | break;
98 | }
99 | if (underMacro) {
100 | var varMatch = Regex.Match(line, TEXT_WITH_WS_REGEX);
101 | if (varMatch.Success && varMatch.Groups["leading_whitespace"].ToString() != lastWhiteSpace) {
102 | sel.GotoLine(onLine + 1, false);
103 | sel.DeleteWhitespace(EnvDTE.vsWhitespaceOptions.vsWhitespaceOptionsHorizontal);
104 | sel.Insert(lastWhiteSpace);
105 | underMacro = false;
106 | // This line has been modified, so change it for the MACRO_REGEX matching below
107 | line = lastWhiteSpace + line.Trim();
108 | }
109 | }
110 | // Its important to check if the current line is a macro
111 | // even if we just fixed the spacing of the current line
112 | var macroMatch = Regex.Match(line, MACRO_REGEX);
113 | if (macroMatch.Success) {
114 | underMacro = true;
115 | lastWhiteSpace = macroMatch.Groups["leading_whitespace"].ToString();
116 | } else if (Regex.Match(line, @"\S+").Success) {
117 | underMacro = false;
118 | }
119 | }
120 | } finally {
121 | if (openedUndoContext) {
122 | _DTE.UndoContext.Close();
123 | }
124 | }
125 | }
126 |
127 | private void AfterKeyPress(string key, TextSelection sel, bool completion) {
128 | // Only semicolons or carriage returns should cause an indentation that need to be fixed
129 | if (key != ";" && key != "\r") {
130 | return;
131 | }
132 | // Make sure we're using smart indent
133 | EnvDTE.Properties textEditorC = _DTE.get_Properties("TextEditor", "C/C++");
134 | if ((int)textEditorC.Item("IndentStyle").Value != 2) {
135 | return;
136 | }
137 | if (key == ";") {
138 | // Make sure we're auto-formatting on semicolons
139 | EnvDTE.Properties textEditorCSpecific = _DTE.get_Properties("TextEditor", "C/C++ Specific");
140 | if (!(bool)textEditorCSpecific.Item("AutoFormatOnSemicolon").Value) {
141 | return;
142 | }
143 | }
144 |
145 | var doc = _DTE.ActiveDocument;
146 | var editPoint = sel.ActivePoint.CreateEditPoint();
147 | string macroLine = null;
148 | var macroLineNum = sel.ActivePoint.Line;
149 | // Search up the document from our current line until we find a line with text on it
150 | // so we can then determine if this line contains a macro we care about
151 | var found = false;
152 | while (--macroLineNum >= 1) {
153 | macroLine = editPoint.GetLines(macroLineNum, macroLineNum + 1);
154 | if (!Regex.IsMatch(macroLine, @"\S+")) {
155 | continue;
156 | }
157 | found = true;
158 | break;
159 | }
160 | if (!found) {
161 | return;
162 | }
163 | var macroMatch = Regex.Match(macroLine, MACRO_REGEX);
164 | if (macroMatch.Success) {
165 | // Goto and select our current line, can't do this in a single GotoLine call for some reason
166 | sel.GotoLine(sel.ActivePoint.Line, false);
167 | sel.SelectLine();
168 | if (Regex.IsMatch(sel.Text, @"\S+")) {
169 | // If the line below the macro has text, undo the indent it just did
170 | doc.Undo();
171 | } else {
172 | // If the line below the macro is empty, add matching whitespace to the beginning of this line to match it up with the macro
173 | sel.MoveToPoint(editPoint);
174 | sel.DeleteWhitespace(EnvDTE.vsWhitespaceOptions.vsWhitespaceOptionsHorizontal);
175 | sel.Insert(macroMatch.Groups["leading_whitespace"].ToString());
176 | }
177 | }
178 | }
179 | }
180 |
181 | Extension
182 | CS
183 | v4.0
184 | false
185 | false
186 |
187 |
188 |
189 |
--------------------------------------------------------------------------------
/ue4_smarter_macro_indenting_vs2017-2019.vcmd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 1
7 | UE4 Smarter Macro Indenting
8 |
9 | using EnvDTE;
10 | using EnvDTE80;
11 | using Microsoft.VisualStudio;
12 | using System;
13 | using System.Text.RegularExpressions;
14 | using System.Windows.Forms;
15 |
16 | public class E : VisualCommanderExt.IExtension
17 | {
18 | private EnvDTE80.DTE2 _DTE;
19 | private EnvDTE80.TextDocumentKeyPressEvents _textDocumentKeyPressEvents;
20 | private static readonly string MACRO_REGEX = @"^(?<leading_whitespace>[\s]*)(UPROPERTY|UFUNCTION|GENERATED_(USTRUCT_|UCLASS_|(U|I)INTERFACE_)?BODY)\(.*";
21 | private static readonly string TEXT_WITH_WS_REGEX = @"(?<leading_whitespace>[\s]*)\S+";
22 |
23 | private CommandEvents _pasteEvent;
24 | private string _beforeText;
25 | private int _numSelectedLines;
26 |
27 | public void SetSite(DTE2 DTE, Microsoft.VisualStudio.Shell.Package package) {
28 | _DTE = DTE;
29 | EnvDTE80.Events2 events2 = (EnvDTE80.Events2)DTE.Events;
30 | _textDocumentKeyPressEvents = events2.get_TextDocumentKeyPressEvents(null);
31 | _textDocumentKeyPressEvents.AfterKeyPress += AfterKeyPress;
32 |
33 | var pasteGuid = typeof(VSConstants.VSStd97CmdID).GUID.ToString("B");
34 | var pasteID = (int)VSConstants.VSStd97CmdID.Paste;
35 | _pasteEvent = _DTE.Events.CommandEvents[pasteGuid, pasteID];
36 | _pasteEvent.BeforeExecute += BeforePaste;
37 | _pasteEvent.AfterExecute += AfterPaste;
38 | }
39 |
40 | public void Close() {
41 | _textDocumentKeyPressEvents.AfterKeyPress -= AfterKeyPress;
42 | _pasteEvent.AfterExecute -= AfterPaste;
43 | _pasteEvent.BeforeExecute -= BeforePaste;
44 | }
45 |
46 | private string GetActiveDocumentText() {
47 | TextDocument doc = (TextDocument)(_DTE.ActiveDocument.Object("TextDocument"));
48 | var editPoint = doc.StartPoint.CreateEditPoint();
49 | return editPoint.GetText(doc.EndPoint);
50 | }
51 |
52 | private void BeforePaste(string Guid, int ID, Object CustomIn, Object CustomOut, ref bool CancelDefault) {
53 | _beforeText = GetActiveDocumentText();
54 | TextSelection sel = (TextSelection)_DTE.ActiveDocument.Selection;
55 | _numSelectedLines = sel.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None).Length;
56 | }
57 |
58 | private void AfterPaste(string Guid, int ID, Object CustomIn, Object CustomOut) {
59 | string afterText = GetActiveDocumentText();
60 | string[] beforeLines = _beforeText.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
61 | string[] afterLines = afterText.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
62 | int pasteLength = (afterLines.Length - beforeLines.Length) + _numSelectedLines;
63 | TextSelection sel = (TextSelection)_DTE.ActiveDocument.Selection;
64 | var editPoint = sel.ActivePoint.CreateEditPoint();
65 | bool underMacro = false;
66 | string lastWhiteSpace = "";
67 | bool openedUndoContext = false;
68 | int startPasteLine = sel.ActivePoint.Line - pasteLength;
69 | int macroLineNum = startPasteLine + 1;
70 | // Search up the document from the start of our paste until we find a line with text on it
71 | // so we can then determine if this line contains a macro we care about
72 | while (--macroLineNum >= 1) {
73 | string macroLine = editPoint.GetLines(macroLineNum, macroLineNum + 1);
74 | if (!Regex.IsMatch(macroLine, @"\S+")) {
75 | continue;
76 | }
77 | var macroMatch = Regex.Match(macroLine, MACRO_REGEX);
78 | if (macroMatch.Success) {
79 | underMacro = true;
80 | lastWhiteSpace = macroMatch.Groups["leading_whitespace"].ToString();
81 | }
82 | break;
83 | }
84 |
85 | if (!_DTE.UndoContext.IsOpen) {
86 | openedUndoContext = true;
87 | // Open the UndoContext so all of our changes can be undone with a single undo
88 | _DTE.UndoContext.Open("FixUE4MacroIndents", false);
89 | }
90 | try {
91 | for (int onLine = startPasteLine; onLine < afterLines.Length; onLine++) {
92 | var line = afterLines[onLine];
93 | // We only need to potentially fix the newly pasted lines
94 | if (onLine >= startPasteLine + pasteLength) {
95 | sel.GotoLine(onLine, false);
96 | sel.MoveToPoint(editPoint);
97 | break;
98 | }
99 | if (underMacro) {
100 | var varMatch = Regex.Match(line, TEXT_WITH_WS_REGEX);
101 | if (varMatch.Success && varMatch.Groups["leading_whitespace"].ToString() != lastWhiteSpace) {
102 | sel.GotoLine(onLine + 1, false);
103 | sel.DeleteWhitespace(EnvDTE.vsWhitespaceOptions.vsWhitespaceOptionsHorizontal);
104 | sel.Insert(lastWhiteSpace);
105 | underMacro = false;
106 | // This line has been modified, so change it for the MACRO_REGEX matching below
107 | line = lastWhiteSpace + line.Trim();
108 | }
109 | }
110 | // Its important to check if the current line is a macro
111 | // even if we just fixed the spacing of the current line
112 | var macroMatch = Regex.Match(line, MACRO_REGEX);
113 | if (macroMatch.Success) {
114 | underMacro = true;
115 | lastWhiteSpace = macroMatch.Groups["leading_whitespace"].ToString();
116 | } else if (Regex.Match(line, @"\S+").Success) {
117 | underMacro = false;
118 | }
119 | }
120 | } finally {
121 | if (openedUndoContext) {
122 | _DTE.UndoContext.Close();
123 | }
124 | }
125 | }
126 |
127 | private void AfterKeyPress(string key, TextSelection sel, bool completion) {
128 | // Only semicolons or carriage returns should cause an indentation that need to be fixed
129 | if (key != ";" && key != "\r") {
130 | return;
131 | }
132 | // Make sure we're using smart indent
133 | EnvDTE.Properties textEditorC = _DTE.get_Properties("TextEditor", "C/C++");
134 | if ((int)textEditorC.Item("IndentStyle").Value != 2) {
135 | return;
136 | }
137 | if (key == ";") {
138 | // Make sure we're auto-formatting on semicolons
139 | EnvDTE.Properties textEditorCSpecific = _DTE.get_Properties("TextEditor", "C/C++ Specific");
140 | if (!(bool)textEditorCSpecific.Item("AutoFormatOnSemicolon").Value) {
141 | return;
142 | }
143 | }
144 |
145 | var doc = _DTE.ActiveDocument;
146 | var editPoint = sel.ActivePoint.CreateEditPoint();
147 | string macroLine = null;
148 | var macroLineNum = sel.ActivePoint.Line;
149 | // Search up the document from our current line until we find a line with text on it
150 | // so we can then determine if this line contains a macro we care about
151 | var found = false;
152 | while (--macroLineNum >= 1) {
153 | macroLine = editPoint.GetLines(macroLineNum, macroLineNum + 1);
154 | if (!Regex.IsMatch(macroLine, @"\S+")) {
155 | continue;
156 | }
157 | found = true;
158 | break;
159 | }
160 | if (!found) {
161 | return;
162 | }
163 | var macroMatch = Regex.Match(macroLine, MACRO_REGEX);
164 | if (macroMatch.Success) {
165 | // Goto and select our current line, can't do this in a single GotoLine call for some reason
166 | sel.GotoLine(sel.ActivePoint.Line, false);
167 | sel.SelectLine();
168 | if (Regex.IsMatch(sel.Text, @"\S+")) {
169 | // If the line below the macro has text, undo the indent it just did
170 | doc.Undo();
171 | } else {
172 | // If the line below the macro is empty, add matching whitespace to the beginning of this line to match it up with the macro
173 | sel.MoveToPoint(editPoint);
174 | sel.DeleteWhitespace(EnvDTE.vsWhitespaceOptions.vsWhitespaceOptionsHorizontal);
175 | sel.Insert(macroMatch.Groups["leading_whitespace"].ToString());
176 | }
177 | }
178 | }
179 | }
180 |
181 | Microsoft.VisualStudio.Shell.Framework
182 |
183 | Extension
184 | CS
185 | v4.0
186 | true
187 | false
188 |
189 |
190 |
191 |
--------------------------------------------------------------------------------