howl.inspect
local buffer, idle_inspector, save_inspector
before_each ->
mode.register name: 'inspect-mode', create: -> {}
idle_inspector = spy.new -> {}
save_inspector = spy.new -> {}
inspection.register name: 'test-idle-inspector', factory: -> idle_inspector
inspection.register name: 'test-save-inspector', factory: -> save_inspector
buffer = Buffer mode.by_name('inspect-mode')
after_each ->
mode.unregister 'inspect-mode'
inspection.unregister 'test-idle-inspector'
inspection.unregister 'test-save-inspector'
inspect(buffer, scope)
runs inspectors specified for the buffer
buffer.config.inspectors_on_idle = {'test-idle-inspector'}
inspect.inspect(buffer)
assert.spy(idle_inspector).was_called_with(match.is_ref(buffer))
runs inspectors specified for the mode
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
inspect.inspect(buffer)
assert.spy(idle_inspector).was_called_with(match.is_ref(buffer))
merges inspection results into one scathing result
inspection.register name: 'inspector1', factory: ->
-> { { line: 1, type: 'error', message: 'foo' } }
inspection.register name: 'inspector2', factory: ->
-> {
{ line: 1, type: 'error', message: 'foo_mode' }
{ line: 3, type: 'warning', message: 'bar' }
}
buffer.config.inspectors_on_idle = {'inspector1', 'inspector2'}
res = inspect.inspect(buffer)
assert.same {
[1]: {
{ type: 'error', message: 'foo' },
{ type: 'error', message: 'foo_mode' }
}
[3]: {
{ type: 'warning', message: 'bar' }
}
}, res
(inspector types)
before_each ->
with buffer.mode.config
.inspectors_on_idle = {'test-idle-inspector'}
.inspectors_on_save = {'test-save-inspector'}
runs both idle and save inspectors by default
inspect.inspect(buffer)
assert.spy(idle_inspector).was_called_with(match.is_ref(buffer))
assert.spy(save_inspector).was_called_with(match.is_ref(buffer))
runs only idle inspectors if specified
inspect.inspect(buffer, scope: 'idle')
assert.spy(idle_inspector).was_called_with(match.is_ref(buffer))
assert.spy(save_inspector).was_not_called!
runs only save inspectors if specified
inspect.inspect(buffer, scope: 'save')
assert.spy(save_inspector).was_called_with(match.is_ref(buffer))
assert.spy(idle_inspector).was_not_called!
(when the returned inspector is a string)
it 'is run as an external command, translating default output parsing', (done) ->
idle_inspector = 'echo "foo:1: warning: foo\nline 2: wrong val \\`foo\\`"'
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
res = inspect.inspect(buffer)
assert.same {
[1]: {
{ message: 'warning: foo', type: 'warning' },
}
[2]: {
{ message: 'wrong val `foo`', search: 'foo' }
}
}, res
done!
it 'ignores output for other files', (done) ->
buffer.file = File '/my/file'
idle_inspector = 'echo "/my/file:1: warning: foo\n/other/file:2: wrong val \\`foo\\`"'
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
res = inspect.inspect(buffer)
assert.same {
[1]: {
{ message: 'warning: foo', type: 'warning' },
}
}, res
done!
it 'keeps output for entries without a file', (done) ->
buffer.file = File '/my/file'
idle_inspector = 'echo 1: foo'
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
res = inspect.inspect(buffer)
assert.same {
[1]: {
{ message: 'foo' },
}
}, res
done!
(when the returned inspector is a table)
it 'uses the `cmd` key as the external command to run', (done) ->
idle_inspector = cmd: 'echo "foo:1: some warning"'
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
res = inspect.inspect(buffer)
assert.same {
[1]: {
{ message: 'some warning' },
}
}, res
done!
it 'allows for custom parsing via the `parse` key', (done) ->
idle_inspector = {
cmd: 'echo "output"'
parse: spy.new -> { {line: 1, message: 'foo' } }
}
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
res = inspect.inspect(buffer)
assert.spy(idle_inspector.parse).was_called_with('output\n')
assert.same {
[1]: {
{ message: 'foo' },
}
}, res
done!
it 'allows for custom post processing via the `post_parse` key', (done) ->
idle_inspector = {
cmd: 'echo "foo:1: some warning"'
post_parse: (inspections) -> inspections[1].search = 'zed'
}
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
res = inspect.inspect(buffer)
assert.same {
[1]: {
{ message: 'some warning', search: 'zed' },
}
}, res
done!
it 'allows for specifying the type of inspections via the `type` key', (done) ->
idle_inspector = cmd: 'echo "foo:1: some warning"', type: 'warning'
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
res = inspect.inspect(buffer)
assert.same {
[1]: {
{ message: 'some warning', type: 'warning' },
}
}, res
done!
it 'skips unavailable inspectors', (done) ->
idle_inspector = {
cmd: 'echo "foo:1: some warning"',
is_available: -> false
}
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
res = inspect.inspect(buffer)
assert.same {}, res
done!
(when an inspector command contains a <file> placeholder)
it "is skipped if the buffer has no associated file", (done) ->
buffer.modified = false
idle_inspector = 'echo "foo:1: <file> urk"'
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
assert.same {}, inspect.inspect(buffer)
done!
it "is skipped if the buffer is modified", (done) ->
file = File '/foo/bar'
buffer.file = file
buffer.modified = true
idle_inspector = 'echo "foo:1: <file> urk"'
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
assert.same {}, inspect.inspect(buffer)
done!
it "is expanded with the buffer's file's path", (done) ->
file = File '/foo/bar'
buffer.file = file
idle_inspector = cmd: 'echo "/foo/bar:1: <file>"'
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
res = inspect.inspect(buffer)
assert.same {
[1]: {
{ message: '/foo/bar' },
}
}, res
done!
it 'is not passed the buffer content on stdin', (done) ->
file = File '/foo/bar'
buffer.file = file
buffer.text = 'bar:1: mem'
buffer.modified = false
idle_inspector = cmd: 'cat <file> -'
howl_async ->
buffer.mode.config.inspectors_on_idle = {'test-idle-inspector'}
res = inspect.inspect(buffer)
assert.same {}, res
done!
criticize(buffer, criticism, opts)
before_each ->
buffer.text = 'linƏ 1\nline 2\nline 3'
applies inspect markers to the buffer corresponding to criticisms
inspect.criticize buffer, {
[1]: {
{type: 'error', message: 'bar'}
},
[2]: {
{type: 'error', message: 'zed'}
}
}
assert.same {
{
start_offset: 1,
end_offset: 7,
name: 'inspection',
flair: 'error',
message: 'bar'
},
{
start_offset: 8,
end_offset: 14,
name: 'inspection',
flair: 'error',
message: 'zed'
}
}, buffer.markers.all
leaves previous inspection markers alone by default
inspect.criticize buffer, {
[1]: { {type: 'error', message: 'bar'} }
}
inspect.criticize buffer, {
[2]: { {type: 'error', message: 'zed'} }
}
assert.equal 2, #buffer.markers.all
clears previous inspection markers when opts.clear is set
inspect.criticize buffer, {
[1]: { {type: 'error', message: 'bar'} }
}
inspect.criticize buffer, {
[2]: { {type: 'error', message: 'zed'} }
}, clear: true
assert.equal 1, #buffer.markers.all
assert.equal 8, buffer.markers.all[1].start_offset
starts the visual marker at the start of text for line inspections
buffer.text = ' 34567\n'
inspect.criticize buffer, {
[1]: {
{type: 'error', message: 'zed'}
}
}
assert.equal 3, buffer.markers.all[1].start_offset
starts the visual marker at the unicode pos given by .start_col if present
buffer.text = 'åäö\n'
inspect.criticize buffer, {
[1]: {
{type: 'error', message: 'zed', start_col: 2}
}
}
assert.equal 2, buffer.markers.all[1].start_offset
ends the visual marker at the unicode pos given by .end_col if present
buffer.text = 'åäö\n'
inspect.criticize buffer, {
[1]: {
{type: 'error', message: 'zed', end_col: 3}
}
}
assert.equal 3, buffer.markers.all[1].end_offset
starts the visual marker at the byte offset given by .byte_start_col if present
buffer.text = 'åäö\n'
inspect.criticize buffer, {
[1]: {
{type: 'error', message: 'zed', byte_start_col: 3}
}
}
assert.equal 2, buffer.markers.all[1].start_offset
ends the visual marker at the byte offset given by .byte_end_col if present
buffer.text = 'åäö\n'
inspect.criticize buffer, {
[1]: {
{type: 'error', message: 'zed', byte_end_col: 3}
}
}
assert.equal 2, buffer.markers.all[1].end_offset
when a .search field is present
is used for selecting a part of the line to highlight
buffer.text = '1 345 7\n'
inspect.criticize buffer, {
[1]: {
{type: 'error', message: 'zed', search: '345'}
}
}
marker = buffer.markers.all[1]
assert.equal 3, marker.start_offset
assert.equal 6, marker.end_offset
marks the whole line if the search fails
buffer.text = '1234567\n'
inspect.criticize buffer, {
[1]: {
{type: 'error', message: 'zed', search: 'XX'}
}
}
marker = buffer.markers.all[1]
assert.equal 1, marker.start_offset
assert.equal 8, marker.end_offset
marks the whole line if the search has multiple matches
buffer.text = 'foo foo\n'
inspect.criticize buffer, {
[1]: {
{type: 'error', message: 'zed', search: 'oo'}
}
}
marker = buffer.markers.all[1]
assert.equal 1, marker.start_offset
assert.equal 8, marker.end_offset
is not confused by other substring matches
buffer.text = ' res = refresh!\n'
inspect.criticize buffer, {
[1]: {
{type: 'error', message: 'zed', search: 'res'}
}
}
marker = buffer.markers.all[1]
assert.equal 2, marker.start_offset
assert.equal 5, marker.end_offset
publish(buffer, source, items)
before_each ->
buffer.text = 'åäö 1\nline 2\nline 3'
lsp_markers = -> buffer.markers\find name: 'inspection', source: 'lsp'
marks the items, tagged with the source
inspect.publish buffer, 'lsp', {
{ line: 1, byte_start_col: 3, byte_end_col: 7, type: 'warning', message: 'bar' }
}
assert.same {
{
start_offset: 2,
end_offset: 4,
name: 'inspection',
flair: 'warning',
message: 'bar',
source: 'lsp'
}
}, buffer.markers.all
ends the marker on .end_line if given, clamped to the last line
inspect.publish buffer, 'lsp', {
{ line: 1, byte_start_col: 1, end_line: 2, byte_end_col: 3, message: 'a' }
{ line: 2, byte_start_col: 1, end_line: 9, byte_end_col: 3, message: 'b' }
}
markers = lsp_markers!
assert.same {1, 9}, {markers[1].start_offset, markers[1].end_offset}
assert.same {7, 16}, {markers[2].start_offset, markers[2].end_offset}
clamps byte columns to the line
inspect.publish buffer, 'lsp', {
{ line: 2, byte_start_col: 3, byte_end_col: 42, message: 'a' }
}
marker = lsp_markers![1]
assert.same {9, 13}, {marker.start_offset, marker.end_offset}
widens empty ranges to one character
inspect.publish buffer, 'lsp', {
{ line: 2, byte_start_col: 3, byte_end_col: 3, message: 'a' }
{ line: 2, byte_start_col: 7, byte_end_col: 7, message: 'b' }
}
markers = lsp_markers!
assert.same {9, 10}, {markers[1].start_offset, markers[1].end_offset}
assert.same {12, 13}, {markers[2].start_offset, markers[2].end_offset}
replaces previous markers from the same source only
inspect.criticize buffer, { [1]: { {message: 'pulled'} } }
inspect.publish buffer, 'other', { {line: 3, message: 'other'} }
inspect.publish buffer, 'lsp', { {line: 1, message: 'old'} }
inspect.publish buffer, 'lsp', { {line: 2, message: 'new'} }
messages = [m.message for m in *buffer.markers.all]
table.sort messages
assert.same {'new', 'other', 'pulled'}, messages
clears the markers from the source when given no items
inspect.publish buffer, 'lsp', { {line: 1, message: 'old'} }
inspect.publish buffer, 'lsp', {}
assert.same {}, buffer.markers.all
adds no markers when auto_inspect is off
buffer.config.auto_inspect = 'off'
inspect.publish buffer, 'lsp', { {line: 1, message: 'old'} }
assert.same {}, buffer.markers.all
the cursor-goto-inspection command
local editor, select_opts
before_each ->
editor = howl.app.editor
howl.app.editor = :buffer
howl.interact.select_location = (opts) -> select_opts = opts
after_each ->
howl.app.editor = editor
howl.interact.select_location = nil
lists the first line of the message for each inspection
buffer.text = 'line 1\nline 2'
inspect.publish buffer, 'lsp', {
{ line: 2, byte_start_col: 1, byte_end_col: 3, message: 'first\nmore' }
{ line: 2, byte_start_col: 4, byte_end_col: 5, message: 'second' }
}
howl.command.get('cursor-goto-inspection').input {}
rows = [{item[1], tostring(item[2]), item[3]} for item in *select_opts.items]
assert.same { {'2', 'line 2', 'first'}, {'·', 'line 2', 'second'} }, rows