Skip to content
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ lets = see = what := happens
a = a = b = c : d := e
```

## See it in action

![atom-alignment](http://papermoon1978.github.io/atom-alignment.gif)

## License

MIT © [Andre Lerche](https://github.com/papermoon1978)
Expand Down
155 changes: 117 additions & 38 deletions lib/aligner.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,42 @@

_ = require 'lodash'

class Ignorelist
#public
constructor: (string, regexp) ->
@__ignoreList = []
if regexp?
while match = regexp.exec(string)
@__ignoreList.push {start:match.index, end:match.index + match[0].length}

contains: (matcher, idx) =>
found = false
_.forEach @__ignoreList, (ignore) ->
if ignore.start <= idx && ignore.end >= idx + matcher.length
found = true
return false

return found

class SafeRegExp
#public
constructor: (parts) ->
_.each parts, (part, idx) ->
if part == "|"
parts[idx] = "\\"+part

@__regex = new RegExp(parts.join("|"), "g")

exec: (s) =>
@__regex.exec(s)

module.exports =
class Aligner
# Public
constructor: (@editor, @spaceChars, @matcher, @addSpacePostfix) ->
constructor: (@editor, @leftAlignChars, @leftSpaceChars, @rightSpaceChars, @matcher, @ignoreChars) ->
@rows = []
@alignments = []
@ignoreRegexp = new SafeRegExp(@ignoreChars) if @ignoreChars.length > 0

# Private
__getRows: =>
Expand All @@ -25,16 +55,17 @@ module.exports =
for cursor in cursors
row = cursor.getBufferRow()
t = @editor.lineTextForBufferRow(row)
l = @__computeLength(t.substring(0,cursor.getBufferColumn()))
v = cursor.getBufferColumn()
l = @__computeLength(t.substring(0, v))
o =
text : t
length : t.length
row : row
column : l
virtualColumn: cursor.getBufferColumn()
virtualColumn: v
@rows.push (o)

else
rowNums = []
ranges = @editor.getSelectedBufferRanges()
for range in ranges
rowNums = rowNums.concat(range.getRows())
Expand All @@ -56,41 +87,75 @@ module.exports =
firstCharIdx = o.text.indexOf(t.charAt(0))
o.text = o.text.substr(0,firstCharIdx) + o.text.substring(firstCharIdx).replace(/\ {2,}/g, ' ')

__getAllIndexes: (string, val, indexes) ->
found = []
i = 0
loop
i = string.indexOf(val, i)
if i != -1 && !_.some(indexes, {index:i})
found.push({found:val,index:i})
return
return

__getAllIndexes: (string) =>
ignoreList = new Ignorelist string, @ignoreRegexp
allMatcherRegEx = new SafeRegExp(@matcher) if @matcher.length > 0
allMatcher = []
if allMatcherRegEx?
while match = allMatcherRegEx.exec(string)
if !ignoreList.contains(match, match.index)
allMatcher.push {matcher: match[0], start:match.index, end:match.index + match[0].length}

return allMatcher

__getNextMatcher: (start, matcher, text) =>
allMatcher = @__getAllIndexes(text)

canUseMatcher = (i) ->
found = false
_.forEach allMatcher, (m) ->
if m.start == i && m.end == i + matcher.length && m.matcher == matcher
found = true
return false

return found

ret = -1
idx = text.indexOf matcher, start
while idx >= 0
if canUseMatcher idx
ret = idx
break
else
start = idx + 1

idx = text.indexOf matcher, start

break if i == -1
i++
return found
return ret

#generate the sequence of alignment characters computed from the first matching line
__generateAlignmentList: () =>
if @mode == "cursor"
_.forEach @rows, (o) =>
part = o.text.substring(o.virtualColumn)
_.forEach @spaceChars, (char) ->
_.forEach @leftSpaceChars, (char) ->
idx = part.indexOf(char)
if idx == 0 && o.text.charAt(o.virtualColumn) != " "
o.addSpacePrefix = true
o.spaceCharLength = char.length
o.leftSpace = true
o.leftSpaceCharLength = char.length
return false

_.forEach @rightSpaceChars, (char) ->
idx = part.indexOf(char)
if idx == 0 && o.text.charAt(o.virtualColumn)+char.length != " "
o.rightSpace = true
o.rightSpaceCharLength = char.length
return false

return
else
_.forEach @rows, (o) =>
_.forEach @matcher, (possibleMatcher) =>
@alignments = @alignments.concat (@__getAllIndexes o.text, possibleMatcher, @alignments)

if @alignments.length > 0
return false # exit if we got all alignments characters in the row
else
return true # continue
@alignments = @alignments.sort (a, b) -> a.index - b.index
@alignments = _.pluck @alignments, "found"
newAlignments = @__getAllIndexes(o.text)

if newAlignments.length > @alignments.length
@alignments = newAlignments.slice()

@alignments = @alignments.sort (a, b) -> a.start - b.start
@alignments = _.pluck @alignments, "matcher"
console.log("atom-alignment: normal mode -> matcher: #{@alignments}")
return

__computeLength: (s) =>
Expand All @@ -112,14 +177,15 @@ module.exports =
matched = null
idx = -1
possibleMatcher = @alignments.shift()
addSpacePrefix = @spaceChars.indexOf(possibleMatcher) > -1
leftSpace = @leftSpaceChars.indexOf(possibleMatcher) > -1
rightSpace = @rightSpaceChars.indexOf(possibleMatcher) > -1
@rows.forEach (o) =>
o.splited = null
if !o.done
line = o.text
if (line.indexOf(possibleMatcher, o.nextPos) != -1)
idx = @__getNextMatcher(o.nextPos, possibleMatcher, line)
if (idx != -1)
matched = possibleMatcher
idx = line.indexOf(matched, o.nextPos)
len = matched.length
if @mode == "break"
idx += len-1
Expand All @@ -144,11 +210,13 @@ module.exports =

if idx isnt -1
splitString = [line.substring(0,idx), line.substring(idx+next)]
splitString[0] = splitString[0].trimRight() if not leftSpace
splitString[1] = splitString[1].trimLeft() if not rightSpace
o.splited = splitString
l = @__computeLength(splitString[0])
if max <= l
max = l
max++ if l > 0 && addSpacePrefix && splitString[0].charAt(splitString[0].length-1) != " "
max++ if l > 0 && leftSpace && splitString[0].charAt(splitString[0].length-1) != " "

found = false
_.forEach @alignments, (nextPossibleMatcher) ->
Expand All @@ -168,13 +236,22 @@ module.exports =
splitString = o.splited
diff = max - @__computeLength(splitString[0])
if diff > 0
splitString[0] = splitString[0] + Array(diff).join(' ')

splitString[1] = " "+splitString[1].trim() if @addSpacePostfix && addSpacePrefix
if matched in @leftAlignChars
splitString[1] = Array(diff).join(' ') + splitString[1].trim()
splitString[0] = splitString[0].trim()
else
splitString[0] = splitString[0].trim() + Array(diff).join(' ')
splitString[1] = splitString[1].trim()

if leftSpace
splitString[0] = splitString[0] + ' '
if rightSpace
splitString[1] = ' ' + splitString[1]

if @mode == "break"
_.forEach splitString, (s, i) ->
splitString[i] = s.trim()
return

o.text = splitString.join("\n")
else
Expand All @@ -188,7 +265,7 @@ module.exports =
if max <= o.column
max = o.column
part = o.text.substring(0,o.virtualColumn)
max++ if part.length > 0 && o.addSpacePrefix && part.charAt(part.length-1) != " "
max++ if part.length > 0 && o.leftSpace && part.charAt(part.length-1) != " "
return

max++
Expand All @@ -200,10 +277,11 @@ module.exports =
if diff > 0
splitString[0] = splitString[0] + Array(diff).join(' ')

o.spaceCharLength ?= 0
splitString[1] = splitString[1].substring(0, o.spaceCharLength) + splitString[1].substr(o.spaceCharLength).trim()
if @addSpacePostfix && o.addSpacePrefix
splitString[1] = splitString[1].substring(0, o.spaceCharLength) + " " +splitString[1].substr(o.spaceCharLength)
o.leftSpaceCharLength ?= 0
o.rightSpaceCharLength ?= 0
splitString[1] = splitString[1].substring(0, o.leftSpaceCharLength) + splitString[1].substr(o.leftSpaceCharLength).trim()
if o.rightSpace
splitString[1] = splitString[1].substring(0, o.rightSpaceCharLength) + " " +splitString[1].substr(o.rightSpaceCharLength)

o.text = splitString.join("")
return
Expand All @@ -225,3 +303,4 @@ module.exports =

@rows.forEach (o) =>
@editor.setTextInBufferRange([[o.row, 0],[o.row, o.length]], o.text)
return
67 changes: 38 additions & 29 deletions lib/atom-alignment.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,48 +2,57 @@ Aligner = require './aligner'

module.exports =
config:
alignmentSpaceChars:
alignBy:
type: 'array'
default: ['=>', ':=', '=']
default: ['=>', ':=', ':', '=']
items:
type: "string"
description: "consider the order, the left most matching separator is taken to compute the alignment"
order: 1
leftAlignChars:
type: 'array'
default: [':']
items:
type: "string"
description: "insert space in front of the character (a=1 > a =1)"
description: "when aligning, keep these characters aligned to the left"
order: 2
alignBy:
leftSpaceChars:
type: 'array'
default: ['=>', ':=', ':', '=']
default: ['=>', ':=', '=']
items:
type: "string"
description: "consider the order, the left most matching value is taken to compute the alignment"
order: 1
addSpacePostfix:
type: 'boolean'
default: false
description: "insert space after the matching character (a=1 > a= 1) if character is part of the 'alignment space chars'"
description: "insert space left of the separator (a=1 > a =1)"
order: 3
rightSpaceChars:
type: 'array'
default: ['=>', ':=', '=', ":"]
items:
type: "string"
description: "insert space right of the separator (a=1 > a= 1)"
order: 4
ignoreChars:
type: 'array'
default: ['===', '!==', '==', '!=', '>=', '<=', '::']
items:
type: "string"
description: "ignore as separator"
order: 5

activate: (state) ->
atom.commands.add 'atom-workspace',
'atom-alignment:align': ->
editor = atom.workspace.getActivePaneItem()
alignLines editor
alignLines false

'atom-alignment:alignMultiple': ->
editor = atom.workspace.getActivePaneItem()
alignLinesMultiple editor

alignLines = (editor) ->
spaceChars = atom.config.get 'atom-alignment.alignmentSpaceChars'
matcher = atom.config.get 'atom-alignment.alignBy'
addSpacePostfix = atom.config.get 'atom-alignment.addSpacePostfix'
a = new Aligner(editor, spaceChars, matcher, addSpacePostfix)
a.align(false)
return
alignLines true

alignLinesMultiple = (editor) ->
spaceChars = atom.config.get 'atom-alignment.alignmentSpaceChars'
matcher = atom.config.get 'atom-alignment.alignBy'
addSpacePostfix = atom.config.get 'atom-alignment.addSpacePostfix'
a = new Aligner(editor, spaceChars, matcher, addSpacePostfix)
a.align(true)
alignLines = (multiple) ->
editor = atom.workspace.getActiveTextEditor()
leftAlignChars = atom.config.get('atom-alignment.leftAlignChars', scope: editor.getRootScopeDescriptor())
leftSpaceChars = atom.config.get('atom-alignment.leftSpaceChars', scope: editor.getRootScopeDescriptor())
rightSpaceChars = atom.config.get('atom-alignment.rightSpaceChars', scope: editor.getRootScopeDescriptor())
matcher = atom.config.get('atom-alignment.alignBy', scope: editor.getRootScopeDescriptor())
ignoreChars = atom.config.get('atom-alignment.ignoreChars', scope: editor.getRootScopeDescriptor())
aligner = new Aligner(editor, leftAlignChars, leftSpaceChars, rightSpaceChars, matcher, ignoreChars)
aligner.align(multiple)
return
5 changes: 1 addition & 4 deletions menus/atom-alignment.cson
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
# See https://atom.io/docs/latest/creating-a-package#menus for more details
'context-menu':
'.overlayer':[{'Enable atom-alignment': 'atom-alignment:align'}]

'menu': [
{
'label': 'Packages'
'submenu': [
'label': 'atom-alignment'
'label': 'Atom Alignment'
'submenu': [
{ 'label': 'Align', 'command': 'atom-alignment:align' },
{ 'label': 'Align Multiple', 'command': 'atom-alignment:alignMultiple' }
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"name": "atom-alignment",
"main": "./lib/atom-alignment",
"version": "0.12.1",
"version": "0.13.0",
"private": true,
"description": "A simple key-binding for aligning multi-line and multiple selections in Atom (Based on the sublime text plugin)",
"repository": "https://github.com/Freyskeyd/atom-alignment",
"repository": "https://github.com/crshd/atom-alignment",
"license": "MIT",
"engines": {
"atom": ">0.50.0"
Expand Down
Loading