Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display Stack Trace in Debug Pane #76

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ Current features of this package:

Future features:
- Interactive object inspection
- Stack trace in debugger
- "Compile this file" command
- "Who calls this function" command

Expand Down
51 changes: 47 additions & 4 deletions lib/atom-slime-debugger-view.coffee
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{CompositeDisposable} = require 'atom'
{$, $$, TextEditorView, View, SelectListView, ScrollView} = require 'atom-space-pen-views'
FrameInfoView = require './atom-slime-frame-info'

module.exports =
class DebuggerView extends ScrollView
Expand All @@ -23,13 +24,24 @@ class DebuggerView extends ScrollView
@li class:"", =>
@button class:"inline-block-tight btn", "Option 3"
@text "Description of option 3"
@h3 "Stack Trace:"
@div class:"select-list", =>
@ol outlet:"stackTrace", class:'list-group mark-active', start:"0", =>
@li class:"", =>
@text "Description of frame 1"
@li class:"", =>
@text "Description of frame 2"
@li class:"", =>
@text "Description of frame 3"
@button outlet:"fullStackTrace", class:"inline-block-tight btn", "Show All Stack Frames"


setup: (@swank, @info) ->
setup: (@swank, @info, @replView) ->
@errorTitle.html @info.title
@errorType.html @info.type
level = @info.level
thread = @info.thread
@active = true

@restarts.empty()
for restart, i in @info.restarts
@restarts.append $$ ->
Expand All @@ -40,14 +52,45 @@ class DebuggerView extends ScrollView
this.find('.restart-button').on 'click', (event) =>
@restart_click_handler event

@render_stack_trace(@info.stack_frames)

restart_click_handler: (event) ->
restartindex = event.target.getAttribute('restartindex')
level = event.target.getAttribute('level')
thread = event.target.getAttribute('thread')
@active = false
@swank.debug_invoke_restart(level, restartindex, thread)

load_full_stack_trace: (event) ->
@fullStackTrace.remove()
thread = @info.thread
@swank.debug_get_stack_trace(thread).then (stack_trace) =>
@info.stack_frame = stack_trace
@render_stack_trace(stack_trace)

render_stack_trace: (trace) =>
@stackTrace.empty()
thread = @info.thread
for frame, i in trace
@stackTrace.append $$ ->
@li class:"", =>
@button class:'inline-block-tight frame-info-button btn', frame_index:frame.frame_number, thread:thread, "Frame Info"
@text i + ": " + frame.description

this.find('.frame-info-button ').on 'click', (event) =>
@view_frame_click_handler event

view_frame_click_handler: (event) ->
frame_index = event.target.getAttribute('frame_index')
thread = thread
if not @frame_info?
@frame_info = new FrameInfoView
@frame_info.setup(@swank, @info, Number(frame_index), @)

atom.workspace.open('slime://debug/'[email protected]+'/frame')


getTitle: -> "Debugger"
getURI: -> "slime://debug" # TODO -- fix
getURI: -> "slime://debug/"[email protected]
isEqual: (other) ->
other instanceof DebuggerView
other instanceof DebuggerView and other.info.level == @info.level
173 changes: 173 additions & 0 deletions lib/atom-slime-frame-info.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
{CompositeDisposable} = require 'atom'
{$, $$, View, SelectListView, ScrollView} = require 'atom-space-pen-views'

module.exports =
class FrameInfoView extends ScrollView
@content: ->
@div outlet:'main', class:'atom-slime-debugger padded', =>
@h1 outlet:'frameName', 'Frame Name'
@div class:'select-list', =>
@ol outlet:'navigation', class:'list-group mark-active', =>
@li 'Navigate to adjacent stack frames'
@h3 'Local Variables'
@div class:'select-list', =>
@ol outlet:'locals', class:'list-group mark-active', =>
@li 'Description of var 0'
@div outlet: 'catchTagsDiv', class:'select-list', =>
@h3 'Catch Tags'
@ol outlet:'catchTags', start:'0', =>
@li 'Description of tag 0'
@div class:'select-list', =>
@ol class:'list-group mark-active', =>
@li =>
@button outlet:'viewSource', class:'inline-block-tight btn', 'View Frame Source'
@li =>
@button outlet:'restartFrame', class:'inline-block-tight btn', 'Restart Frame'
@li =>
@button outlet:'disassemble', class:'inline-block-tight btn', 'Disassemble Frame'
@div outlet:'disassembleDiv', =>
@h3 'Disassembled:'
@span outlet:'disassembleOutput', class:'slime-message', ''
@ol class:'list-group mark-active', =>
@li =>
@input outlet:'frameReturnValue', class:'native-key-bindings', type:'text', size:50
@li =>
@button outlet:'returnFromFrame', class:'inline-block-tight btn', 'Return From Frame'
@li =>
@button outlet:'evalInFrame', class:'inline-block-tight btn', 'Eval in Frame'

setup: (@swank, @info, @frame_index, @debugView) ->
frame = @info.stack_frames[@frame_index]

@frameName.html @frame_index + ': ' + frame.description

@navigation.empty()
if @frame_index > 0
@add_navigation_item(0, description = @info.stack_frames[0].description, 'Stack Top')
if @frame_index > 1
@add_navigation_item(@frame_index-1, description = @info.stack_frames[@frame_index-1].description, 'Up')
if @frame_index < @info.stack_frames.length - 1
@add_navigation_item(@frame_index+1, description = @info.stack_frames[@frame_index+1].description, 'Down')

this.find('.frame-navigation-button').on 'click', (event) =>
@setup(@swank, @info, Number(event.target.getAttribute('frame_index')), @debugView)

@viewSource.on 'click', (event) =>
@display_frame_source()

if frame.restartable
@restartFrame[0].disabled = false
@restartFrame.on 'click', (event) =>
@debugView.active = false
@swank.debug_restart_frame(@frame_index, @info.thread)
else
@restartFrame[0].disabled = true

@disassembleDiv.hide()
@disassemble.on 'click', (event) =>
@swank.debug_disassemble_frame(@frame_index, @info.thread).then (output) =>
@disassembleOutput.text(output)
@disassembleDiv.show()

@returnFromFrame.on 'click', (event) =>
@debugView.active = false
@swank.debug_return_from_frame(@frame_index, @frameReturnValue.val(), @info.thread)
.catch (errorMessage) =>
atom.notifications.addError(errorMessage)

@evalInFrame.on 'click', (event) =>
input = @frameReturnValue.val()
@frameReturnValue.val('')
@swank.debug_eval_in_frame(@frame_index, input, @info.thread).then (result) =>
replView = @debugView.replView
replView.print_string_callback(result+'\n')
replView.replPane.activateItem(replView.editor)

@swank.debug_stack_frame_details(@frame_index, @info.stack_frames, @info.thread).then (frame) =>
@locals.empty()
if frame.locals.length > 0
for local, i in frame.locals
@locals.append $$ ->
@li local.id + ': ' + local.name + ' = ' + local.value
else
@locals.append $$ ->
@li '<No Locals>'
if frame.catch_tags.length > 0
@catchTagsDiv.show()
@catchTags.empty()
for tag, i in frame.catch_tags
@catchTags.append $$ ->
@li i + ': ' + tag
else
@catchTagsDiv.hide()

add_navigation_item: (index, frame_description, label) ->
@navigation.append $$ ->
@li class:"", =>
@button class:'inline-block-tight frame-navigation-button btn', frame_index:index, label
@text index+": " + frame_description

display_frame_source: () =>
frame_index = @frame_index
@swank.debug_frame_source(frame_index, @info.thread).then (source_location) ->
if source_location.buffer_type == 'error'
editor_promise = Promise.reject(source_location.error)
else if source_location.buffer_type == 'buffer' or source_location.buffer_type == 'buffer-and-file'
# look through editors for an editor with a matching name
for editor in atom.workspace.getTextEditors()
if editor.getTitle() == source_location.buffer_name
pane = atom.workspace.paneForItem(editor)
pane.activate()
pane.activateItem(editor)
editor_promise = Promise.resolve(editor)
break
if source_location.buffer_type == 'buffer-and-file'
# fall back to file if nessacery
editor_promise ?= atom.workspace.open(source_location.file)
else
editor_promise ?= Promise.reject('No editor named '+source_location.buffer_name)
else if source_location.buffer_type == 'file'
editor_promise = atom.workspace.open(source_location.file)
else if source_location.buffer_type == 'source-form'
editor = atom.workspace.buildTextEditor()
editor.setText(source_location.source_form)
#change default title
editor.getTitle = -> editor.getFileName() ? 'Frame ' + frame_index + ' Source'
#change condition for save prompt for unsaved file
editor_buffer = editor.getBuffer()
editor_buffer.isModified = ->
if editor_buffer.file?.existsSync()
editor_buffer.buffer.isModified()
else
editor_buffer.getText() != source_location.source_form
#add to active pane
activePane = atom.workspace.getActivePane()
activePane.addItem(editor)
activePane.activateItem(editor)
editor_promise = Promise.resolve(editor)
else
# TODO zip
editor_promise = Promise.reject('Unsupported source type given: "'+source_location.buffer_type+'"')

#need source_location, so use a closure to pass it to the next part
editor_promise.then (editor) ->
if source_location.position_type == 'line'
row = source_location.position_line
col = location.position_column ? 0
editor.setCursorBufferPosition(new Point(row, col))
else if source_location.position_type == 'position'
position = editor.getBuffer()
.positionForCharacterIndex(source_location.position_offset)
editor.setCursorBufferPosition(position)
else
#TODO function-name
#TODO source-path
#TODO method
Promise.reject('Unsupported position type given: "'+source_location.position_type+'"')
.catch (error) ->
atom.notifications.addError 'Cannot show frame source: '+error

getTitle: -> 'Frame Info'
getURI: => 'slime://debug/' + @info.level + '/frame'
isEqual: (other) =>
other instanceof FrameInfoView and @info.level == other.info.level
59 changes: 38 additions & 21 deletions lib/atom-slime-repl-view.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,9 @@ class REPLView
# Debug functions
@swank.on 'debug_setup', (obj) => @createDebugTab(obj)
@swank.on 'debug_activate', (obj) =>
# TODO - keep track of differnet levels
@showDebugTab()
@showDebugTab(obj.level)
@swank.on 'debug_return', (obj) =>
# TODO - keep track of different levels
@closeDebugTab()
@closeDebugTab(obj.level)

# Profile functions
@swank.on 'profile_command_complete', (msg) =>
Expand Down Expand Up @@ -315,28 +313,47 @@ class REPLView


setupDebugger: () ->
@dbgv = []
process.nextTick =>
@subs.add atom.workspace.addOpener (filePath) =>
if filePath == 'slime://debug'
return @dbgv
if filePath.match(///^slime://debug/\d+$///)
level = filePath.slice(14)
return @dbgv[level-1]
if filePath.match(///^slime://debug/\d+/frame$///)
level = filePath.slice(14, -6)
return @dbgv[level-1].frame_info
@subs.add @replPane.onWillDestroyItem (e) =>
if e.item == @dbgv
@swank.debug_escape_all()
if e.item in @dbgv
level = e.item.info.level #1 based indices
if level < @dbgv.length
#recursively delete lower levels
@closeDebugTab(level+1)
if e.item.active
@swank.debug_abort_current_level(e.item.level, e.item.info.thread)
e.item.active = false
if e.item.frame_info?
@replPane.destroyItem(e.item.frame_info)


createDebugTab: (obj) ->
@dbgv = new DebuggerView
@dbgv.setup(@swank, obj)

showDebugTab: () ->
@replPane.activate()
atom.workspace.open('slime://debug').then (d) =>
# TODO - doesn't work
#elt = atom.views.getView(d)
#atom.commands.add elt, 'q': (event) => @closeDebugTab()

closeDebugTab: () ->
@replPane.destroyItem(@dbgv)
if obj.level > @dbgv.length
@dbgv.push(new DebuggerView)
debug = @dbgv[obj.level-1]
debug.setup(@swank, obj, @)

showDebugTab: (level) ->
# A slight pause is needed before showing for when an error occurs immediatly after resolving another error
setTimeout(() =>
@replPane.activate()
atom.workspace.open('slime://debug/'+level).then (d) =>
# TODO - doesn't work
#elt = atom.views.getView(d)
#atom.commands.add elt, 'q': (event) => @closeDebugTab(level)
, 10)

closeDebugTab: (level) ->
@replPane.destroyItem(@dbgv[level-1])
@dbgv.pop()


# Set the package and prompt
Expand All @@ -347,6 +364,6 @@ class REPLView

destroy: ->
if @swank.connected
@closeDebugTab()
@closeDebugTab(1)
@subs.dispose()
@swank.quit()
6 changes: 6 additions & 0 deletions styles/atom-slime.less
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,9 @@ bubble-multiline-message {
.slime-object-introspected h4 {
border-bottom: solid 1px @base-border-color;
}


// frame info
.slime-message {
white-space: pre-line;
}