Issue #29 Command and file autocomplete
This commit is contained in:
parent
dfa44b5fa2
commit
7e1e03284a
4 changed files with 169 additions and 0 deletions
66
lib/autocomplete.coffee
Normal file
66
lib/autocomplete.coffee
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
fs = require 'fs'
|
||||||
|
path = require 'path'
|
||||||
|
Ex = require './ex'
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
class AutoComplete
|
||||||
|
constructor: (commands) ->
|
||||||
|
@commands = commands
|
||||||
|
@resetCompletion()
|
||||||
|
|
||||||
|
resetCompletion: () ->
|
||||||
|
@autoCompleteIndex = 0
|
||||||
|
@autoCompleteText = null
|
||||||
|
@completions = []
|
||||||
|
|
||||||
|
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.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) ->
|
||||||
|
if @completions.length == 0
|
||||||
|
return @filterByPrefix(@commands, command)
|
||||||
|
|
||||||
|
getFilePathCompletion: (command, 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'
|
{ViewModel, Input} = require './view-model'
|
||||||
|
AutoComplete = require './autocomplete'
|
||||||
|
Ex = require './ex'
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
class ExViewModel extends ViewModel
|
class ExViewModel extends ViewModel
|
||||||
|
|
@ -6,15 +8,31 @@ class ExViewModel extends ViewModel
|
||||||
super(@exCommand, class: 'command')
|
super(@exCommand, class: 'command')
|
||||||
@historyIndex = -1
|
@historyIndex = -1
|
||||||
|
|
||||||
|
@view.editorElement.addEventListener('keydown', @tabAutocomplete)
|
||||||
atom.commands.add(@view.editorElement, 'core:move-up', @increaseHistoryEx)
|
atom.commands.add(@view.editorElement, 'core:move-up', @increaseHistoryEx)
|
||||||
atom.commands.add(@view.editorElement, 'core:move-down', @decreaseHistoryEx)
|
atom.commands.add(@view.editorElement, 'core:move-down', @decreaseHistoryEx)
|
||||||
|
|
||||||
|
@autoComplete = new AutoComplete(Ex.getCommands())
|
||||||
|
|
||||||
restoreHistory: (index) ->
|
restoreHistory: (index) ->
|
||||||
@view.editorElement.getModel().setText(@history(index).value)
|
@view.editorElement.getModel().setText(@history(index).value)
|
||||||
|
|
||||||
history: (index) ->
|
history: (index) ->
|
||||||
@exState.getExHistoryItem(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: =>
|
increaseHistoryEx: =>
|
||||||
if @history(@historyIndex + 1)?
|
if @history(@historyIndex + 1)?
|
||||||
@historyIndex += 1
|
@historyIndex += 1
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,9 @@ class Ex
|
||||||
@registerAlias: (alias, name) =>
|
@registerAlias: (alias, name) =>
|
||||||
@singleton()[alias] = (args) => @singleton()[name](args)
|
@singleton()[alias] = (args) => @singleton()[name](args)
|
||||||
|
|
||||||
|
@getCommands: () =>
|
||||||
|
Object.keys(@singleton())
|
||||||
|
|
||||||
quit: ->
|
quit: ->
|
||||||
atom.workspace.getActivePane().destroyActiveItem()
|
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