aboutsummaryrefslogtreecommitdiff
path: root/atom/packages/ex-mode/lib/command.coffee
diff options
context:
space:
mode:
Diffstat (limited to 'atom/packages/ex-mode/lib/command.coffee')
-rw-r--r--atom/packages/ex-mode/lib/command.coffee169
1 files changed, 169 insertions, 0 deletions
diff --git a/atom/packages/ex-mode/lib/command.coffee b/atom/packages/ex-mode/lib/command.coffee
new file mode 100644
index 0000000..115b273
--- /dev/null
+++ b/atom/packages/ex-mode/lib/command.coffee
@@ -0,0 +1,169 @@
+ExViewModel = require './ex-view-model'
+Ex = require './ex'
+Find = require './find'
+CommandError = require './command-error'
+
+class Command
+ constructor: (@editor, @exState) ->
+ @viewModel = new ExViewModel(@)
+
+ parseAddr: (str, curPos) ->
+ if str is '.'
+ addr = curPos.row
+ else if str is '$'
+ # Lines are 0-indexed in Atom, but 1-indexed in vim.
+ addr = @editor.getBuffer().lines.length - 1
+ else if str[0] in ["+", "-"]
+ addr = curPos.row + @parseOffset(str)
+ else if not isNaN(str)
+ addr = parseInt(str) - 1
+ else if str[0] is "'" # Parse Mark...
+ unless @vimState?
+ throw new CommandError("Couldn't get access to vim-mode.")
+ mark = @vimState.marks[str[1]]
+ unless mark?
+ throw new CommandError("Mark #{str} not set.")
+ addr = mark.bufferMarker.range.end.row
+ else if str[0] is "/"
+ addr = Find.findNextInBuffer(@editor.buffer, curPos, str[1...-1])
+ unless addr?
+ throw new CommandError("Pattern not found: #{str[1...-1]}")
+ else if str[0] is "?"
+ addr = Find.findPreviousInBuffer(@editor.buffer, curPos, str[1...-1])
+ unless addr?
+ throw new CommandError("Pattern not found: #{str[1...-1]}")
+
+ return addr
+
+ parseOffset: (str) ->
+ if str.length is 0
+ return 0
+ if str.length is 1
+ o = 1
+ else
+ o = parseInt(str[1..])
+ if str[0] is '+'
+ return o
+ else
+ return -o
+
+ execute: (input) ->
+ @vimState = @exState.globalExState.vim?.getEditorState(@editor)
+ # Command line parsing (mostly) following the rules at
+ # http://pubs.opengroup.org/onlinepubs/9699919799/utilities
+ # /ex.html#tag_20_40_13_03
+
+ # Steps 1/2: Leading blanks and colons are ignored.
+ cl = input.characters
+ cl = cl.replace(/^(:|\s)*/, '')
+ return unless cl.length > 0
+
+ # Step 3: If the first character is a ", ignore the rest of the line
+ if cl[0] is '"'
+ return
+
+ # Step 4: Address parsing
+ lastLine = @editor.getBuffer().lines.length - 1
+ if cl[0] is '%'
+ range = [0, lastLine]
+ cl = cl[1..]
+ else
+ addrPattern = ///^
+ (?: # First address
+ (
+ \.| # Current line
+ \$| # Last line
+ \d+| # n-th line
+ '[\[\]<>'`"^.(){}a-zA-Z]| # Marks
+ /.*?[^\\]/| # Regex
+ \?.*?[^\\]\?| # Backwards search
+ [+-]\d* # Current line +/- a number of lines
+ )((?:\s*[+-]\d*)*) # Line offset
+ )?
+ (?:, # Second address
+ ( # Same as first address
+ \.|
+ \$|
+ \d+|
+ '[\[\]<>'`"^.(){}a-zA-Z]|
+ /.*?[^\\]/|
+ \?.*?[^\\]\?|
+ [+-]\d*
+ )((?:\s*[+-]\d*)*)
+ )?
+ ///
+
+ [match, addr1, off1, addr2, off2] = cl.match(addrPattern)
+
+ curPos = @editor.getCursorBufferPosition()
+
+ if addr1?
+ address1 = @parseAddr(addr1, curPos)
+ else
+ # If no addr1 is given (,+3), assume it is '.'
+ address1 = curPos.row
+ if off1?
+ address1 += @parseOffset(off1)
+
+ address1 = 0 if address1 is -1
+
+ if address1 < 0 or address1 > lastLine
+ throw new CommandError('Invalid range')
+
+ if addr2?
+ address2 = @parseAddr(addr2, curPos)
+ if off2?
+ address2 += @parseOffset(off2)
+
+ if address2 < 0 or address2 > lastLine
+ throw new CommandError('Invalid range')
+
+ if address2 < address1
+ throw new CommandError('Backwards range given')
+
+ range = [address1, if address2? then address2 else address1]
+ cl = cl[match?.length..]
+
+ # Step 5: Leading blanks are ignored
+ cl = cl.trimLeft()
+
+ # Step 6a: If no command is specified, go to the last specified address
+ if cl.length is 0
+ @editor.setCursorBufferPosition([range[1], 0])
+ return
+
+ # Ignore steps 6b and 6c since they only make sense for print commands and
+ # print doesn't make sense
+
+ # Ignore step 7a since flags are only useful for print
+
+ # Step 7b: :k<valid mark> is equal to :mark <valid mark> - only a-zA-Z is
+ # in vim-mode for now
+ if cl.length is 2 and cl[0] is 'k' and /[a-z]/i.test(cl[1])
+ command = 'mark'
+ args = cl[1]
+ else if not /[a-z]/i.test(cl[0])
+ command = cl[0]
+ args = cl[1..]
+ else
+ [m, command, args] = cl.match(/^(\w+)(.*)/)
+
+ # If the command matches an existing one exactly, execute that one
+ if (func = Ex.singleton()[command])?
+ func(range, args)
+ else
+ # Step 8: Match command against existing commands
+ matching = (name for name, val of Ex.singleton() when \
+ name.indexOf(command) is 0)
+
+ matching.sort()
+
+ command = matching[0]
+
+ func = Ex.singleton()[command]
+ if func?
+ func(range, args)
+ else
+ throw new CommandError("Not an editor command: #{input.characters}")
+
+module.exports = Command