Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Table Alignment for Multiple Selections per Line #7

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable changes to the "cursor-align" extension will be documented in this f
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- Added table align for multiselections per line.

## [1.0.4] - 2017-09-06
### Changed
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ Aligns all of the cursors/selections into a vertical line by inserting spaces. A

![Select Align Demo 2](img/selectAlignDemo2.gif)

### Table Align

![Table Align Demo](img/tableAlignDemo.gif)


## Extension Commands

Expand All @@ -27,14 +31,12 @@ Aligns all of the cursors/selections into a vertical line by inserting spaces. A

**Note:** A "cursor" is actually a selection with a length of 0.

Inserts spaces to the left of selections until all the selection starts are aligned with the right-most selection start. Adds spaces to the end of selections until all selections are the same length as the longest selection (all section ends are aligned with the right most selection end).
For each line with selections is marked the positions of the start and end of each selection. Next is inserted spaces at the left of the first marked position of each line until all the first positions are align. Repeat the same for the second positions, and so on.

### Caveats

Multiline selections don't make much sense with this tool, but here's how they are handled: A multiline selection is split into two 0-length selections with one at the starting point and one at the end point of the multiline selection.

Likewise, multiple selections per line don't make much sense, but here's how they are handled: Multiple selections per line are combined into a single selection that starts at the left-most selection's starting point and ends at the right-most selection's end point.


## alt+a Not Working?

Expand Down
125 changes: 38 additions & 87 deletions extension/allignBlockHelper.js
Original file line number Diff line number Diff line change
@@ -1,106 +1,57 @@
/**
* 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.
* the blocks of text that should be aligned for each line.
* @param {vscode.Selection} selections Selections to create align blocks from.
* @returns {{line: number, cursors: number[]}}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;
// selections are not ordered
const alignBlocks = new Map();

// index for insert in a sorted array
function sortedIndex(array, value) {
var low = 0,
high = array.length;

while (low < high) {
var mid = (low + high) >>> 1;
if (array[mid] <= value) low = mid + 1;
else high = mid;
}
return low;
}

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;
function getBlock(line) {
let block = alignBlocks.get(line);
if (block == undefined) { // if the block don't exist yet, create it
block = { line, cursors: [] };
alignBlocks.set(line, block);
}
return block;
}

return maxBlockStartChar;
}
for (let i = 0; i < selections.length; i++) {
const selection = selections[i];

/**
* 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;
// get the block of this line and push the start and end of the block
let block = getBlock(selection.start.line);
let start = selection.start.character;
let end = selection.isSingleLine ? selection.end.character : start;
// insert start and end in the array, sorted
block.cursors.splice(sortedIndex(block.cursors, start), 0, start, end);

// create two blocks 0-length blocks at the start and end for multi-line selections
if (!selection.isSingleLine) {
block = getBlock(selection.end.line);
let pos = selection.end.character;
// insert start and end in the array, sorted
block.cursors.splice(sortedIndex(block.cursors, start), 0, pos, pos);
}
}

return maxBlockLength;
return Array.from(alignBlocks.values());
}


module.exports = {
createAlignBlocksFromSelections,
createAlignBlock,
getMaxAlignBlockStartChar,
getMaxAlignBlockLength
};
17 changes: 5 additions & 12 deletions extension/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ function alignCursors() {

// get all the blocks of text that will be aligned from the selections
const alignBlocks = alignBlockHelper.createAlignBlocksFromSelections(textEditor.selections);

if (alignBlocks.length < 2) {
return;
}

const targetStartChar = alignBlockHelper.getMaxAlignBlockStartChar(alignBlocks);
const targetLength = alignBlockHelper.getMaxAlignBlockLength (alignBlocks);

// calculate where we should insert spaces
const spaceInserts = spaceInsertHelper.createSpaceInsertsFromAlignBlocks(alignBlocks, targetStartChar, targetLength);
// calculate where we should insert spaces, and get the selections of each block
const [spaceInserts, selections] = spaceInsertHelper.createSpaceInsertsAndSelectionsFromAlignBlocks(alignBlocks);

if (spaceInserts.length === 0) {
return;
}
Expand All @@ -50,13 +49,7 @@ function alignCursors() {
}, {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.selections = selections;

textEditor.edit(textEditorEdit => {
// noop
Expand Down
65 changes: 41 additions & 24 deletions extension/spaceInsertHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,52 @@ const vscode = require('vscode');


/**
* 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.
* Creates space inserts to align the given align blocks and selections for each block.
* Space Inserts hold spaces and the position to insert them.
* @param {{line: number, cursors: number[]}[]} alignBlocks Align blocks to align.
* @param {number} targetStartChar Starting character to align the blocks to.
* @param {number} targetLength Length to align the blocks to.
* @returns {[{ pos: Position; str: string; }[], vscode.Selection[]]} [space inserts, selections]
*/
function createSpaceInsertsFromAlignBlocks(alignBlocks, targetStartChar, targetLength) {
function createSpaceInsertsAndSelectionsFromAlignBlocks(alignBlocks) {
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));
const selections = [];
let lastAlign = 0;
// align all the first cursors in each line, after this all the seconds, and so on...
// if some line don't have the j-th cursor, this line is removed from the array
OUTER: for (let j = 0; alignBlocks.length > 1; j++) {
// find the target aling position
let targetChar = -1;
INNER: for (let i = 0; i < alignBlocks.length; i++) {
while (alignBlocks[i].cursors.length == j) { // if cursor[j] don't exist, remove the line from the list
alignBlocks.splice(i,1);
if (i == alignBlocks.length) {
if (alignBlocks.length <= 1) break OUTER; // if the number of alingBLocks is 1, so there is no need for more alingments
else break INNER;
}
}
let last = j == 0 ? 0 : alignBlocks[i].cursors[j-1];
let cur = alignBlocks[i].cursors[j] - last + lastAlign; // cur is the current position of the cursor after the last alingment
if (cur > targetChar) {
targetChar = cur;
}
}
if (endDist > 0) {
// insert spaces after the align block to align the right side
spaceInserts.push(createSpaceInsert(alignBlock.line, alignBlock.endChar, endDist));

for (let i = 0; i < alignBlocks.length; i++) {
let last = j == 0 ? 0 : alignBlocks[i].cursors[j-1];
let cur = alignBlocks[i].cursors[j] - last + lastAlign; // cur is the current position of the cursor after the last alingment
let dist = targetChar - cur;
if (dist != 0) {
spaceInserts.push(createSpaceInsert(alignBlocks[i].line, alignBlocks[i].cursors[j], dist));
}
if (j%2 == 1) { // each pair even-odd is a selection
selections.push(new vscode.Selection(alignBlocks[i].line, lastAlign, alignBlocks[i].line, targetChar));
}
}
lastAlign = targetChar;
}

return spaceInserts;
return [spaceInserts, selections];
}

/**
Expand All @@ -48,6 +66,5 @@ function createSpaceInsert(line, startChar, dist) {


module.exports = {
createSpaceInsertsFromAlignBlocks,
createSpaceInsert
createSpaceInsertsAndSelectionsFromAlignBlocks,
};
Binary file added img/tableAlignDemo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.