-
Notifications
You must be signed in to change notification settings - Fork 8
/
extension.js
228 lines (188 loc) · 7.25 KB
/
extension.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
const vscode = require('vscode');
/**
* Aligns all cursors in the active text editor by inserting spaces.
*/
function alignCursors() {
// make sure we have an active text editor
// NOTE: we use registerCommand instead of registerTextEditorCommand because we
// need greater control over the TextEditorEdit
const textEditor = vscode.window.activeTextEditor;
if (!textEditor) {
return;
}
// get all the blocks of text that will be aligned from the selections
const alignBlocks = createAlignBlocksFromSelections(textEditor.selections);
if (alignBlocks.length < 2) {
return;
}
const targetStartChar = getMaxAlignBlockStartChar(alignBlocks);
const targetLength = getMaxAlignBlockLength (alignBlocks);
// calculate where we should insert spaces
const spaceInserts = createSpaceInsertsFromAlignBlocks(alignBlocks, targetStartChar, targetLength);
if (spaceInserts.length === 0) {
return;
}
// NOTE: I'm really not sure how the undo system works. Especially regarding
// selections.
//
// For example, if you undo and redo a command, the text changes are undone and
// redone correctly, but the selections are not. The selections do not change
// when you redo the command. However, if you put a second edit at the end of
// your command, this fixes the issue (even if the edit does not do anything).
//
// Also, if we do 2 edits and either one or both of the edits create an
// undo stop, then 2 undos are required to completely undo the command.
// However, if neither edit creates an undo stop, then 1 undo is required to
// completely undo the command.
// start the edit
textEditor.edit(textEditorEdit => {
// insert all of the spaces
spaceInserts.forEach(spaceInsert => textEditorEdit.insert(spaceInsert.pos, spaceInsert.str));
}, {undoStopBefore: false, undoStopAfter: false}) // don't create an undo after (before does not seem to matter)
.then(() => {
// select all the aligned blocks
textEditor.selections = alignBlocks.map(alignBlock => {
const line = alignBlock.line;
const startChar = targetStartChar;
const endChar = targetStartChar + targetLength;
return new vscode.Selection(line, startChar, line, endChar);
});
textEditor.edit(textEditorEdit => {
// noop
}, {undoStopBefore: false, undoStopAfter: false}); // don't create an undo stop before (after does not seem to matter)
}, err => {
throw err;
});
}
module.exports = {
activate(context) {
// NOTE: we use registerCommand instead of registerTextEditorCommand because we
// need greater control over the TextEditorEdit
context.subscriptions.push(vscode.commands.registerCommand('yo1dog.cursor-align.alignCursors', alignCursors));
},
deactivate() {
},
alignCursors
};
/**
* Creates align blocks from the given selections. Align blocks represent
* the blocks of text that should be aligned.
* @param {vscode-Selection} selections Selections to create align blocks from.
* @returns Align blocks.
*/
function createAlignBlocksFromSelections(selections) {
const alignBlocks = [];
// create align blocks for each selection
for (let i = 0; i < selections.length; ++i) {
const selection = selections[i];
if (selection.isSingleLine) {
// create one block for single-line selections
alignBlocks.push(createAlignBlock(selection.start.line, selection.start.character, selection.end.character));
}
else {
// create two blocks 0-length blocks at the start and end for multi-line selections
alignBlocks.push(createAlignBlock(selection.start.line, selection.start.character, selection.start.character));
alignBlocks.push(createAlignBlock(selection.end .line, selection.end .character, selection.end .character));
}
}
// combine align blocks that are on the same line
for (let i = 1; i < alignBlocks.length; ++i) {
for (let j = 0; j < i; ++j) {
// check if two blocks are on the same line
if (alignBlocks[j].line !== alignBlocks[i].line) {
continue;
}
// combine the blocks by using the min start char and the max end char
alignBlocks[j].startChar = Math.min(alignBlocks[j].startChar, alignBlocks[i].startChar);
alignBlocks[j].endChar = Math.max(alignBlocks[j].endChar, alignBlocks[i].endChar );
alignBlocks.splice(i, 1);
--i;
break;
}
}
return alignBlocks;
}
/**
* Creates an align block.
* @param {number} line Line of the align block.
* @param {number} startChar Starting character of the align block.
* @param {number} endChar Ending character of the align block.
* @returns Align block.
*/
function createAlignBlock(line, startChar, endChar) {
return {
line,
startChar,
endChar
};
}
/**
* Gets the right-most starting character of the given align blocks.
* @param {Object[]} alignBlocks
* @returns {number} Right-most (max) starting character.
*/
function getMaxAlignBlockStartChar(alignBlocks) {
let maxBlockStartChar = -1;
for (let i = 0; i < alignBlocks.length; ++i) {
const alignBlock = alignBlocks[i];
if (alignBlock.startChar > maxBlockStartChar) {
maxBlockStartChar = alignBlock.startChar;
}
}
return maxBlockStartChar;
}
/**
* Gets the longest length of the given align blocks.
* @param {Object[]} alignBlocks
* @returns {number} Longest (max) length.
*/
function getMaxAlignBlockLength(alignBlocks) {
let maxBlockLength = -1;
for (let i = 0; i < alignBlocks.length; ++i) {
const alignBlock = alignBlocks[i];
const blockLength = alignBlock.endChar - alignBlock.startChar;
if (blockLength > maxBlockLength) {
maxBlockLength = blockLength;
}
}
return maxBlockLength;
}
/**
* Creates space inserts to align the given align blocks. Space Inserts
* hold spaces and the position to insert them.
* @param {Object[]} alignBlocks Align blocks to align.
* @param {number} targetStartChar Starting character to align the blocks to.
* @param {number} targetLength Length to align the blocks to.
*/
function createSpaceInsertsFromAlignBlocks(alignBlocks, targetStartChar, targetLength) {
const spaceInserts = [];
// create space inserts for each align block
for (let i = 0; i < alignBlocks.length; ++i) {
const alignBlock = alignBlocks[i];
const alignBlockLength = alignBlock.endChar - alignBlock.startChar;
const startDist = targetStartChar - alignBlock.startChar;
const endDist = targetLength - alignBlockLength;
if (startDist > 0) {
// insert spaces before the align block to align the left side
spaceInserts.push(createSpaceInsert(alignBlock.line, alignBlock.startChar, startDist));
}
if (endDist > 0) {
// insert spaces after the align block to align the right side
spaceInserts.push(createSpaceInsert(alignBlock.line, alignBlock.endChar, endDist));
}
}
return spaceInserts;
}
/**
* Creates a space insert.
* @param {number} line Line to insert space.
* @param {number} startChar Character position to insert space at.
* @param {number} dist Number of spaces to insert.
* @returns Space insert.
*/
function createSpaceInsert(line, startChar, dist) {
return {
pos: new vscode.Position(line, startChar),
str: ' '.repeat(dist)
};
}