Merge pull request #155 from stuartquin/tab-autocomplete
Issue #29 Command and file autocomplete
This commit is contained in:
commit
25ce6b59fd
4 changed files with 179 additions and 0 deletions
74
lib/autocomplete.coffee
Normal file
74
lib/autocomplete.coffee
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
fs = require 'fs'
|
||||
path = require 'path'
|
||||
os = require 'os'
|
||||
Ex = require './ex'
|
||||
|
||||
module.exports =
|
||||
class AutoComplete
|
||||
constructor: (commands) ->
|
||||
@commands = commands
|
||||
@resetCompletion()
|
||||
|
||||
resetCompletion: () ->
|
||||
@autoCompleteIndex = 0
|
||||
@autoCompleteText = null
|
||||
@completions = []
|
||||
|
||||
expandTilde: (filePath) ->
|
||||
if filePath.charAt(0) == '~'
|
||||
return os.homedir() + filePath.slice(1)
|
||||
else
|
||||
return filePath
|
||||
|
||||
getAutocomplete: (text) ->
|
||||
if !@autoCompleteText
|
||||
@autoCompleteText = text
|
||||
|
||||
parts = @autoCompleteText.split(' ')
|
||||
cmd = parts[0]
|
||||
|
||||
if parts.length > 1
|
||||
filePath = parts.slice(1).join(' ')
|
||||
return @getCompletion(() => @getFilePathCompletion(cmd, filePath))
|
||||
else
|
||||
return @getCompletion(() => @getCommandCompletion(cmd))
|
||||
|
||||
filterByPrefix: (commands, prefix) ->
|
||||
commands.sort().filter((f) => f.startsWith(prefix))
|
||||
|
||||
getCompletion: (completeFunc) ->
|
||||
if @completions.length == 0
|
||||
@completions = completeFunc()
|
||||
|
||||
if @completions.length
|
||||
complete = @completions[@autoCompleteIndex % @completions.length]
|
||||
@autoCompleteIndex++
|
||||
|
||||
# Only one result so lets return this directory
|
||||
if complete.endsWith('/') && @completions.length == 1
|
||||
@resetCompletion()
|
||||
|
||||
return complete
|
||||
|
||||
getCommandCompletion: (command) ->
|
||||
return @filterByPrefix(@commands, command)
|
||||
|
||||
getFilePathCompletion: (command, filePath) ->
|
||||
filePath = @expandTilde(filePath)
|
||||
|
||||
if filePath.endsWith(path.sep)
|
||||
basePath = path.dirname(filePath + '.')
|
||||
baseName = ''
|
||||
else
|
||||
basePath = path.dirname(filePath)
|
||||
baseName = path.basename(filePath)
|
||||
|
||||
files = fs.readdirSync(basePath)
|
||||
|
||||
return @filterByPrefix(files, baseName).map((f) =>
|
||||
filePath = path.join(basePath, f)
|
||||
if fs.lstatSync(filePath).isDirectory()
|
||||
return command + ' ' + filePath + path.sep
|
||||
else
|
||||
return command + ' ' + filePath
|
||||
)
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
{ViewModel, Input} = require './view-model'
|
||||
AutoComplete = require './autocomplete'
|
||||
Ex = require './ex'
|
||||
|
||||
module.exports =
|
||||
class ExViewModel extends ViewModel
|
||||
|
|
@ -6,15 +8,31 @@ class ExViewModel extends ViewModel
|
|||
super(@exCommand, class: 'command')
|
||||
@historyIndex = -1
|
||||
|
||||
@view.editorElement.addEventListener('keydown', @tabAutocomplete)
|
||||
atom.commands.add(@view.editorElement, 'core:move-up', @increaseHistoryEx)
|
||||
atom.commands.add(@view.editorElement, 'core:move-down', @decreaseHistoryEx)
|
||||
|
||||
@autoComplete = new AutoComplete(Ex.getCommands())
|
||||
|
||||
restoreHistory: (index) ->
|
||||
@view.editorElement.getModel().setText(@history(index).value)
|
||||
|
||||
history: (index) ->
|
||||
@exState.getExHistoryItem(index)
|
||||
|
||||
tabAutocomplete: (event) =>
|
||||
if event.keyCode == 9
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
completed = @autoComplete.getAutocomplete(@view.editorElement.getModel().getText())
|
||||
if completed
|
||||
@view.editorElement.getModel().setText(completed)
|
||||
|
||||
return false
|
||||
else
|
||||
@autoComplete.resetCompletion()
|
||||
|
||||
increaseHistoryEx: =>
|
||||
if @history(@historyIndex + 1)?
|
||||
@historyIndex += 1
|
||||
|
|
|
|||
|
|
@ -118,6 +118,11 @@ class Ex
|
|||
@registerAlias: (alias, name) =>
|
||||
@singleton()[alias] = (args) => @singleton()[name](args)
|
||||
|
||||
@getCommands: () =>
|
||||
Object.keys(Ex.singleton()).concat(Object.keys(Ex.prototype)).filter((cmd, index, list) ->
|
||||
list.indexOf(cmd) == index
|
||||
)
|
||||
|
||||
quit: ->
|
||||
atom.workspace.getActivePane().destroyActiveItem()
|
||||
|
||||
|
|
|
|||
82
spec/autocomplete-spec.coffee
Normal file
82
spec/autocomplete-spec.coffee
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
fs = require 'fs-plus'
|
||||
path = require 'path'
|
||||
os = require 'os'
|
||||
uuid = require 'node-uuid'
|
||||
|
||||
helpers = require './spec-helper'
|
||||
AutoComplete = require '../lib/autocomplete'
|
||||
|
||||
describe "autocomplete functionality", ->
|
||||
beforeEach ->
|
||||
@autoComplete = new AutoComplete(['taba', 'tabb', 'tabc'])
|
||||
@testDir = path.join(os.tmpdir(), "atom-ex-mode-spec-#{uuid.v4()}")
|
||||
@testFile1 = path.join(@testDir, "atom-ex-testfile-a.txt")
|
||||
@testFile2 = path.join(@testDir, "atom-ex-testfile-b.txt")
|
||||
|
||||
runs =>
|
||||
fs.makeTreeSync(@testDir)
|
||||
fs.closeSync(fs.openSync(@testFile1, 'w'));
|
||||
fs.closeSync(fs.openSync(@testFile2, 'w'));
|
||||
spyOn(@autoComplete, 'resetCompletion').andCallThrough()
|
||||
spyOn(@autoComplete, 'getFilePathCompletion').andCallThrough()
|
||||
spyOn(@autoComplete, 'getCommandCompletion').andCallThrough()
|
||||
|
||||
afterEach ->
|
||||
fs.removeSync(@testDir)
|
||||
|
||||
describe "autocomplete commands", ->
|
||||
beforeEach ->
|
||||
@completed = @autoComplete.getAutocomplete('tab')
|
||||
|
||||
it "returns taba", ->
|
||||
expect(@completed).toEqual('taba')
|
||||
|
||||
it "calls command function", ->
|
||||
expect(@autoComplete.getCommandCompletion.callCount).toBe(1)
|
||||
|
||||
describe "autocomplete commands, then autoComplete again", ->
|
||||
beforeEach ->
|
||||
@completed = @autoComplete.getAutocomplete('tab')
|
||||
@completed = @autoComplete.getAutocomplete('tab')
|
||||
|
||||
it "returns tabb", ->
|
||||
expect(@completed).toEqual('tabb')
|
||||
|
||||
it "calls command function", ->
|
||||
expect(@autoComplete.getCommandCompletion.callCount).toBe(1)
|
||||
|
||||
describe "autocomplete directory", ->
|
||||
beforeEach ->
|
||||
filePath = path.join(os.tmpdir(), 'atom-ex-mode-spec-')
|
||||
@completed = @autoComplete.getAutocomplete('tabe ' + filePath)
|
||||
|
||||
it "returns testDir", ->
|
||||
expected = 'tabe ' + @testDir + path.sep
|
||||
expect(@completed).toEqual(expected)
|
||||
|
||||
it "clears autocomplete", ->
|
||||
expect(@autoComplete.resetCompletion.callCount).toBe(1)
|
||||
|
||||
describe "autocomplete directory, then autocomplete again", ->
|
||||
beforeEach ->
|
||||
filePath = path.join(os.tmpdir(), 'atom-ex-mode-spec-')
|
||||
@completed = @autoComplete.getAutocomplete('tabe ' + filePath)
|
||||
@completed = @autoComplete.getAutocomplete(@completed)
|
||||
|
||||
it "returns test file 1", ->
|
||||
expect(@completed).toEqual('tabe ' + @testFile1)
|
||||
|
||||
it "lists files twice", ->
|
||||
expect(@autoComplete.getFilePathCompletion.callCount).toBe(2)
|
||||
|
||||
describe "autocomplete full directory, then autocomplete again", ->
|
||||
beforeEach ->
|
||||
filePath = path.join(@testDir, 'a')
|
||||
@completed = @autoComplete.getAutocomplete('tabe ' + filePath)
|
||||
@completed = @autoComplete.getAutocomplete(@completed)
|
||||
|
||||
it "returns test file 2", ->
|
||||
expect(@completed).toEqual('tabe ' + @testFile2)
|
||||
|
||||
it "lists files once", ->
|
||||
expect(@autoComplete.getFilePathCompletion.callCount).toBe(1)
|
||||
Loading…
Add table
Add a link
Reference in a new issue