howl.mode

after_each ->
  for name in *[name for name in *mode.names when name != 'default' ]
    mode.unregister name

mode instances are memoized

mode.register name: 'same', extensions: 'again', create: -> {}
assert.equal mode.by_name('same'), mode.for_file File 'once.again'

mode instances automatically have their .name set

mode.register name: 'named', extensions: 'again', create: -> {}
assert.equal mode.by_name('named').name, 'named'

.names contains all registered mode names and their aliases

mode.register name: 'needle', aliases: 'foo', create: -> {}
assert.includes mode.names, 'needle'
assert.includes mode.names, 'foo'

.register(spec)

raises an error if any of the mandatory inputs are missing

assert.raises 'name', -> mode.register {}
assert.raises 'create', -> mode.register name: 'foo'

.by_name(name)

returns a mode instance for <name>, or nil if not existing

assert.is_nil mode.by_name 'blargh'

mode.register name: 'shish', create: -> {}
assert.is_not_nil mode.by_name('shish')

allows looking up modes by any aliases

assert.is_nil mode.by_name 'my_mode'

mode.register name: 'shish', aliases: {'my_mode'}, create: -> {}
assert.is_not_nil mode.by_name('my_mode')

for_extension(extension)

returns a mode registered for <extension>, if any

mode.register name: 'ext', extensions: 'foo', create: -> {}
assert.equal 'ext', mode.for_extension('foo').name

.for_file(file)

(when the file extension is registered with a mode)

returns an instance of that mode

mode.register name: 'ext', extensions: 'foo', create: -> {}
file = File 'test.foo'
assert.equal 'ext', mode.for_file(file).name

(when the file paths matches a mode pattern)

returns an instance of that mode

mode.register name: 'pattern', patterns: 'match%w+$', create: -> {}
file = File 'matchme'
assert.equal 'pattern', mode.for_file(file).name

(when the file header matches a mode shebang)

returns an instance of that mode

mode.register name: 'shebang', shebangs: 'lua$', create: -> {}
File.with_tmpfile (file) ->
  file.contents = '#! /usr/bin/lua\nother line\nand other\n'
  assert.equal 'shebang', mode.for_file(file).name

(when no matching mode can be found)

returns an instance of the mode "default"

file = File 'test.blargh'
assert.equal 'default', mode.for_file(file).name

(mode creation)

modes are created by calling the modes create function, passing the name

create = spy.new -> {}
mode.register name: 'callme', :create
mode.by_name('callme')
assert.spy(create).was_called_with 'callme'

mode configuration variables

config.define name: 'mode_var', description: 'some var', default: 'def value'

mode instances automatically have their .config set

mode.register name: 'config', create: -> {}
mode_config = mode.by_name('config').config
assert.is_not_nil mode_config

assert.equal 'def value', mode_config.mode_var
mode_config.mode_var = 123
assert.equal 123, mode_config.mode_var
assert.equal 'def value', config.mode_var

the .config is pre-seeded with variables from .default_config of the mode (if any)

mode.register name: 'pre_config', create: -> default_config: { mode_var: 543 }
assert.equal 543, mode.by_name('pre_config').config.mode_var

configure(mode_name, variables)

before_each ->
  mode.register name: 'user_configured', create: -> {}

allows setting mode specific variables automatically upon creation

mode.configure 'user_configured', mode_var: 'from_user'
assert.equal 'from_user', mode.by_name('user_configured').config.mode_var

automatically sets the config variables for any already instantiated mode

mode_config = mode.by_name('user_configured').config
mode.configure 'user_configured', mode_var: 'after_the_fact'
assert.equal 'after_the_fact', mode_config.mode_var

overrides any default mode configuration set

mode.register name: 'mode_with_config', create: -> default_config: { mode_var: 'mode set' }
mode.configure 'mode_with_config', mode_var: 'user set'
assert.equal 'user set', mode.by_name('mode_with_config').config.mode_var

mode inheritance

before_each ->
  base = foo: 'foo'
  mode.register name: 'base', create: -> base
  mode.register name: 'sub', parent: 'base', create: -> {}
  config.define name: 'delegated_mode_var', description: 'some var', default: 'def value'

a mode extending another mode automatically delegates to that mode

assert.equal 'foo', mode.by_name('sub').foo
 mode.by_name('base').config.delegated_mode_var = 123
 assert.equal 123, mode.by_name('sub').config.delegated_mode_var

.parent is set to the mode parent

mode.register name: 'subsub', parent: 'sub', create: -> {}
sub_mode = mode.by_name('sub')
subsub_mode = mode.by_name('subsub')
assert.equal 'base', sub_mode.parent.name
assert.equal 'sub', subsub_mode.parent.name

overridden functions and variables other then parent are respected

mode.register name: 'first', create: -> {
  mode_var1: 'first1'
  mode_var2: 'first2'

  one: => "#{@mode_var1} #{@two!}"
  two: => @mode_var2
}

mode.register name: 'second', parent: 'first', create: -> {
  mode_var1: 'second1'

  two: => "#{@mode_var2}X"
}

second_mode = mode.by_name('second')
assert.equal "second1 first2X", second_mode\one!

an error is raised if the mode indicated by parent does not exist

assert.has_error ->
  mode.register name: 'wrong', parent: 'keyser_soze', create: -> {}
  mode.by_name 'wrong'

parent defaults to "default" unless given

mode.register name: 'orphan', create: -> {}
assert.equal 'default', mode.by_name('orphan').parent.name

super(...)

super methods can be invoked using `super`

mode.register name: 'first', create: -> { foo: => "1" }
mode.register name: 'second', parent: 'first', create: -> {
  foo: => super! .. '2'
}
mode.register name: 'third', parent: 'second', create: -> {
  foo: => super! .. '3'
  other: => 'other'
}

third_mode = mode.by_name('third')
assert.equal '123', third_mode\foo!

raises an error if no parent has the specified method

mode.register name: 'wrong', create: -> {
  foo: => super!
}
assert.raises "No parent 'foo'", ->
  mode.by_name('wrong')\foo!

invokes the super method with the correct parameters

local args
mode.register name: 'first', create: -> {
  foo: (...) -> args = {...}
}
mode.register name: 'second', parent: 'first', create: -> {
  foo: (...) => super ...
}

second = mode.by_name('second')
second\foo '1', 2
assert.same {second, '1', 2}, args

works when deriving from the default mode implicitly

mode.register name: 'implicit', create: -> {
  comment_syntax: '--'
  comment: (editor) => super editor
}
buf = Buffer {}
buf.text = 'line 1\nline2'
editor = Editor buf

mode.by_name('implicit')\comment editor
assert.equal '-- line 1\nline2', buf.text

.unregister(name)

removes the mode specified by <name>

mode.register name: 'mode', aliases: 'foo', extensions: 'zen', create: -> {}
mode.unregister 'mode'
assert.is_nil mode.by_name 'mode'
assert.is_nil mode.by_name 'foo'
assert.equal mode.for_file(File('test.zen')), mode.by_name 'default'

removes any memoized instance

mode.register name: 'memo', extensions: 'memo', create: -> {}
mode.unregister 'memo'
live = mode.by_name 'memo'
mode.register name: 'memo', extensions: 'memo', create: -> {}
assert.is_not_equal live, mode.by_name('memo')