howl.modes.DefaultMode
local buffer, mode, lines, indentation
editor = Editor Buffer {}
cursor = editor.cursor
selection = editor.selection
indent = ->
  selection\select_all!
  mode\indent editor
before_each ->
  buffer = Buffer {}
  mode = DefaultMode!
  indentation = {}
  mode.indentation = indentation
  buffer.mode = mode
  buffer.config.indent = 2
  lines = buffer.lines
  editor.buffer = buffer
.indent()
indentation.more_after = { '{' }
  indentation.less_for = { '}' }
  buffer = ActionBuffer!
  buffer.text = '{\nfoo\n  }\n'
  lines = buffer.lines
  buffer\style lines[1].start_pos, lines[2].end_pos, 'comment'
  buffer\style lines[3].start_pos, lines[4].end_pos, 'string'
  editor.buffer = buffer
  indent!
  assert.equals '{\nfoo\n  }\n', buffer.text
indentation.more_after = { '{' }
  mode.comment_syntax = '#'
  buffer.text = "  # I'm commenting thank you very much {\n# and still are\n"
  indent!
  assert.equals 2, lines[2].indentation
adjust any illegal indentation (not divisable by indent)
buffer.text = '  line\n two\n'
indent!
assert.equals '  line\n  two\n', buffer.text
works on the current line if no selection is specified
indentation.more_after = { 'if' }
buffer.text = 'if\none\ntwo\n'
cursor.line = 2
mode\indent editor
assert.equals 'if\n  one\ntwo\n', buffer.text
moves the cursor to the beginning of indentation if it would be positioned before
buffer.text = '  line1\n line2\n'
cursor.line = 2
mode\indent editor
assert.equals '  line1\n  line2\n', buffer.text
assert.equals 3, cursor.column
(when .indentation.more_after patterns is set)
(..  and the previous line matches one of the patterns)
indents lines below matching lines with the currently set indent
indentation.more_after = { r'if', 'then' }
buffer.text = 'if\nbar\nthen\nfoo\n'
indent!
assert.equals 'if\n  bar\nthen\n  foo\n', buffer.text
(..  when .authoritive is not false)
adjusts lines with unwarranted greater indents to match the previous line
indentation.more_after = { 'if' }
buffer.text = 'line\n  wat?\n'
indent!
assert.equals 'line\nwat?\n', buffer.text
 
(..  when .authoritive is false)
does not adjust lines with unwarranted greater indents to match the previous line
indentation.more_after = { 'if', authoritive: false }
buffer.text = 'line\n  wat?\n'
indent!
assert.equals 'line\n  wat?\n', buffer.text
 
 
 
(when .indentation.less_for is set)
(..  and the current line matches one of the patterns)
dedents the line one level below the previous line if it exists
indentation.less_for = { r'else', '}' }
buffer.text = '    bar\n    else\n  foo\n  }\n'
indent!
assert.equals '    bar\n  else\n  foo\n}\n', buffer.text
(..  when .authoritive is not false)
adjusts lines with unwarranted smaller indents to match the previous line
indentation.less_for = { 'else' }
buffer.text = '  line\nwat?\n'
indent!
assert.equals '  line\n  wat?\n', buffer.text
 
(..  when .authoritive is false)
does not adjust lines with unwarranted smaller indents to match the previous line
indentation.less_for = { 'else', authoritive: false }
buffer.text = '  line\nwat?\n'
indent!
assert.equals '  line\nwat?\n', buffer.text
 
 
 
(when .indentation.more_for is set)
(..  and the current line matches one of the patterns)
indents the line one level right of the previous line if it exists
indentation.more_for = { '^.' }
buffer.text = 'bar\n.foo\n'
indent!
assert.equals 'bar\n  .foo\n', buffer.text
 
 
(when .indentation.same_after patterns is set)
(..  and the previous line matches one of the patterns)
indents lines below matching lines to have the same indent as the previous line
indentation.same_after = ',$'
buffer.text = '  foo,\nbar'
indent!
assert.equals '  foo,\n  bar', buffer.text
 
 
(when more than one of .less_for, .more_after or .same_after are set)
they are weighed together
indentation.more_after = { '{' }
indentation.less_for = { '}' }
indentation.same_after = { ',$' }
buffer.text = '  {\n  }'
indent!
assert.equals '  {\n  }', buffer.text
buffer.text = '  {\n  foo,}'
indent!
assert.equals '  {\n  foo,}', buffer.text
 
(when a line is blank)
does not indent unless it is the current line
indentation.more_after = { '{' }
  indentation.less_for = { '}' }
  buffer.text = '{\n\n}'
  indent!
  assert.equals '{\n\n}', buffer.text
(..  and it is the current line)
indents according to patterns
indentation.more_after = { '{' }
  indentation.less_for = { '}' }
  buffer.text = '{\n\n}'
  cursor.line = 2
  mode\indent editor
  assert.equals '{\n  \n}', buffer.text
sets the same indent as for the previous line if nothing else is specified
buffer.text = '  line\n\n'
cursor.line = 2
mode\indent editor
assert.equals '  line\n  \n', buffer.text
 
 
 
text = [[
  liñe 1
liñe 2
liñe 3
  ]]
before_each ->
  buffer.text = text
  selection\set 1, lines[4].start_pos
(when .comment_syntax is not set)
does nothing
mode\comment editor
assert.equal text, buffer.text
 
(when .comment_syntax is set to a string)
before_each -> mode.comment_syntax = '--'
prefixes the selected lines with the prefix and a space, at the minimum indentation level
mode\comment editor
assert.equal [[
    liñe 3
  ]], buffer.text
selection\remove!
cursor.pos = 1
mode\comment editor
assert.equal [[
    liñe 2
    liñe 3
  ]], buffer.text
honor leading tabs when use_tabs is true
buffer.config.use_tabs = true
buffer.text = '\tline1\n'
cursor.pos = 1
mode\comment editor
assert.equal '<TAB>-- line1\n', buffer.text\gsub('\t', '<TAB>')
keeps the cursor position
editor.selection.cursor = lines[3].start_pos + 2
mode\comment editor
assert.equal 6, cursor.column
 
(when .comment_syntax is set to a pair)
before_each -> mode.comment_syntax = {'/*', '*/'}
wraps each selected line with the pair, at the minimum indentation level
mode\comment editor
assert.equal [[
  /* liñe 1 */
  /*   liñe 2 */
    liñe 3
  ]], buffer.text
selection\remove!
cursor.pos = 1
mode\comment editor
assert.equal [[
  /* liñe 1 */
    liñe 2
    liñe 3
  ]], buffer.text
keeps the cursor position
editor.selection.cursor = lines[3].start_pos + 2
mode\comment editor
assert.equal 6, cursor.column
 
 
(when .comment_syntax is not set)
does nothing
buffer.text = 'foo\nbar\n'
selection\set 1, lines[2].start_pos
mode\uncomment editor
assert.equal 'foo\nbar\n', buffer.text
 
(when .comment_syntax is set to a string)
before_each ->
  buffer.mode.comment_syntax = '--'
  buffer.text = [[
]]
  selection\set 1, lines[3].start_pos
mode\uncomment editor
assert.equal [[
   liñe 1
]], buffer.text
selection\remove!
cursor.line = 2
mode\uncomment editor
assert.equal [[
]], buffer.text
honor leading tabs when use_tabs is true
buffer.config.use_tabs = true
buffer.text = '\t-- line1\n'
cursor.pos = 1
mode\uncomment editor
assert.equal '<TAB>line1\n', buffer.text\gsub('\t', '<TAB>')
keeps the cursor position
editor.selection.cursor = lines[2].start_pos + 6
mode\uncomment editor
assert.equal 4, cursor.column
buffer.text = "line\n"
cursor.line = 1
mode\uncomment editor
assert.equal "line\n", buffer.text
 
(when .comment_syntax is set to a pair)
before_each ->
  buffer.mode.comment_syntax = {'/*', '*/'}
  buffer.text = [[
  /*  liñe 1 */
    /* liñe 2 */
    /*liñe 3*/
]]
  selection\set 1, lines[3].start_pos
mode\uncomment editor
assert.equal [[
   liñe 1
    liñe 2
    /*liñe 3*/
]], buffer.text
selection\remove!
cursor.line = 2
mode\uncomment editor
assert.equal [[
  /*  liñe 1 */
    liñe 2
    /*liñe 3*/
]], buffer.text
keeps the cursor position
editor.selection.cursor = lines[2].start_pos + 6
mode\uncomment editor
assert.equal 4, cursor.column
buffer.text = "line\n"
cursor.line = 1
mode\uncomment editor
assert.equal "line\n", buffer.text
 
 
(when mode does not provide .comment_syntax)
does nothing
buffer.text = '-- foo'
mode\toggle_comment editor
assert.equal '-- foo', buffer.text
 
(when mode provides .comment_syntax)
before_each -> buffer.mode.comment_syntax = '--'
buffer.text = '  -- foo\n\n  -- foo2'
selection\select_all!
mode\toggle_comment editor
assert.equal '  foo\n\n  foo2', buffer.text
buffer.text = 'foo\n-- foo2'
selection\select_all!
mode\toggle_comment editor
assert.equal '-- foo\n-- -- foo2', buffer.text
buffer.text = '-- foo\nfoo2'
selection\select_all!
mode\toggle_comment editor
assert.equal '-- -- foo\n-- foo2', buffer.text
buffer.text = 'foo\nfoo2'
selection\select_all!
mode\toggle_comment editor
assert.equal '-- foo\n-- foo2', buffer.text
 
 
indents the new line automatically given the indent patterns
indentation.more_after = { 'if' }
buffer.text = 'if'
cursor\eof!
editor\newline!
assert.equals 'if\n  ', buffer.text
buffer.text = 'other'
cursor\eof!
editor\newline!
assert.equals 'other\n', buffer.text
 
structure()
assert_lines = (expected, actual) -> assert.same [l.nr for l in *expected], [l.nr for l in *actual]
returns a simple indentation-based structure
buffer.text = [[
  header1
    sub1
      foo
      bar
      zed
      froz
  header2
    sub2
]]
assert_lines {
  lines[1]
  lines[2]
  lines[7]
}, mode\structure editor
the config variable indentation_structure_threshold determines when it stops
buffer.text = [[
  header1
    sub1
      froz
  header2
    sub2
]]
buffer.config.indentation_structure_threshold = 2
assert_lines { lines[1], lines[4] }, mode\structure editor
disregards blank lines
buffer.text = '\n  sub\n'
assert.same {}, mode\structure editor
(if a structure line is all non-alpha)
tries to use the previous line if that contains alpha characters
buffer.text = [[
  int func(int arg)
  {
    froz();
  }
]]
assert_lines { lines[1] }, mode\structure editor
skips the line altogether if the previous line does not contain alpha characters
buffer.text = [[
  {
    froz();
  }
]]
assert_lines {}, mode\structure editor
 
 
patterns_match(text, patterns)
returns a boolean indicating whether <text> matches any of the specified patterns
assert.is_true mode\patterns_match 'foo', { 'foo' }
assert.is_false mode\patterns_match 'foo', { 'bar' }
accepts both Lua patterns and regexes
assert.is_true mode\patterns_match 'foo', { 'fo+' }
assert.is_true mode\patterns_match 'foo', { r'\\pLo*' }
a specifed pattern can be table containing both a positiv and a negative match
p = { 'foo', 'bar' }
assert.is_true mode\patterns_match 'foo zed', { p }
assert.is_false mode\patterns_match 'foo bar', { p }
 
when a newline is added
sets the indentation for the new line to the indentation of the previous non-blank line
buffer.text = '  34\n\n78'
cursor.pos = 7
editor\newline!
assert.equals 2, editor.current_line.indentation
(when .code_blocks.multiline is present)
the code blocks are automatically enforced
mode.code_blocks = multiline: {
  { '%sdo$', '^%s*end', 'end' },
}
buffer.text = 'foo do'
cursor\eof!
editor\newline!
assert.equals 'foo do\n\nend\n', buffer.text
assert.equals 2, cursor.line
buffer.config.auto_format = false
mode.code_blocks = multiline: {
  { '%sdo$', '^%s*end', 'end' },
}
buffer.text = 'foo do'
cursor\eof!
editor\newline!
assert.equals 'foo do\n', buffer.text