From a4f22c3aadcf0ea13d97e5107b04914935e34cf6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 26 Jul 2014 17:00:13 -0400 Subject: [PATCH 01/22] Stacktrace.parse() returns multiple results now. --- lib/main.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/main.coffee b/lib/main.coffee index c0ae98d..0e014f4 100644 --- a/lib/main.coffee +++ b/lib/main.coffee @@ -11,9 +11,9 @@ module.exports = StacktraceView.registerIn(atom.workspace) atom.on 'stacktrace:accept-trace', ({trace}) => - t = Stacktrace.parse(trace) - t.register() - atom.workspace.open t.getUrl() + for trace in Stacktrace.parse(trace) + trace.register() + atom.workspace.open trace.getUrl() deactivate: -> atom.off 'stacktrace:accept-trace' From 45b85067f02231b9d6833dd03c226f64c93b34ab Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 26 Jul 2014 17:11:35 -0400 Subject: [PATCH 02/22] Spec for rendering the error message. --- lib/stacktrace-view.coffee | 2 +- spec/stacktrace-view-spec.coffee | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index 502a184..8bd7617 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -6,7 +6,7 @@ class StacktraceView extends View @content: (trace) -> @div class: 'stacktrace tool-panel padded', => @div class: 'header panel', => - @h2 trace.message + @h2 class: 'error-message', trace.message @div class: 'frames', => for frame in trace.frames @subview 'frame', new FrameView(frame) diff --git a/spec/stacktrace-view-spec.coffee b/spec/stacktrace-view-spec.coffee index df48b26..7a46db0 100644 --- a/spec/stacktrace-view-spec.coffee +++ b/spec/stacktrace-view-spec.coffee @@ -29,7 +29,10 @@ describe 'StacktraceView', -> trace.register() expect(opener(trace.getUrl()).trace).toBe(trace) - it 'shows the error message' + it 'shows the error message', -> + text = view.find('.error-message').text() + expect(text).toEqual('Boom') + it 'renders a subview for each frame' describe 'FrameView', -> From e250a0b3a9961cbf7bd66c45dd46a7b636f23af8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 26 Jul 2014 17:12:46 -0400 Subject: [PATCH 03/22] Rendering a subview for each Frame. --- spec/stacktrace-view-spec.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/stacktrace-view-spec.coffee b/spec/stacktrace-view-spec.coffee index 7a46db0..8b1933b 100644 --- a/spec/stacktrace-view-spec.coffee +++ b/spec/stacktrace-view-spec.coffee @@ -33,7 +33,9 @@ describe 'StacktraceView', -> text = view.find('.error-message').text() expect(text).toEqual('Boom') - it 'renders a subview for each frame' + it 'renders a subview for each frame', -> + vs = view.find('.frame') + expect(vs.length).toBe(3) describe 'FrameView', -> [view] = [] From 3e1c1f7a0b24454e45efc989d066f3ad6ffea7ee Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 26 Jul 2014 17:19:37 -0400 Subject: [PATCH 04/22] Specs for FrameView. --- spec/stacktrace-view-spec.coffee | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/spec/stacktrace-view-spec.coffee b/spec/stacktrace-view-spec.coffee index 8b1933b..e726f4f 100644 --- a/spec/stacktrace-view-spec.coffee +++ b/spec/stacktrace-view-spec.coffee @@ -2,7 +2,7 @@ {Stacktrace, Frame} = require '../lib/stacktrace' frames = [ - new Frame('raw0', 'bottom.rb', 12, 'botfunc', 'Boom') + new Frame('raw0', 'bottom.rb', 12, 'botfunc') new Frame('raw1', 'middle.rb', 42, 'midfunc') new Frame('raw2', 'top.rb', 37, 'topfunc') ] @@ -43,6 +43,11 @@ describe 'FrameView', -> beforeEach -> view = new FrameView(frames[1]) - it 'shows the filename' - it 'shows the line number' - it 'shows the function name' + it 'shows the filename and line number', -> + text = view.find('.source-location').text() + expect(text).toMatch(/middle\.rb/) + expect(text).toMatch(/42/) + + it 'shows the function name', -> + text = view.find('.function-name').text() + expect(text).toEqual('midfunc') From 2387c860bb4ae854273f1c23d5263b020e1b83e6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 26 Jul 2014 17:46:02 -0400 Subject: [PATCH 05/22] Stub out controls for activation and deactivation. --- lib/stacktrace-view.coffee | 13 ++++++++++--- lib/stacktrace.coffee | 8 +++++++- stylesheets/stacktrace.less | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index 8bd7617..c4c09bc 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -4,9 +4,16 @@ class StacktraceView extends View @content: (trace) -> - @div class: 'stacktrace tool-panel padded', => - @div class: 'header panel', => + tclass = if trace.isActive() then 'activated' else 'deactivated' + @div class: "stacktrace tool-panel padded #{tclass}", => + @div class: 'panel padded', => @h2 class: 'error-message', trace.message + @p class: 'activate-control', => + @button class: 'btn btn-primary selected inline-block', click: 'activate', 'Activate' + @span class: 'inline-block', 'to navigate around this stacktrace.' + @p class: 'deactivate-control', => + @button class: 'btn btn-primary inline-block', 'Deactivate' + @span class: 'inline-block', 'to close the stacktrace navigation panel.' @div class: 'frames', => for frame in trace.frames @subview 'frame', new FrameView(frame) @@ -32,7 +39,7 @@ class FrameView extends View @div class: 'panel-heading', => @span class: 'function-name text-highlight inline-block', frame.functionName @span class: 'source-location text-info inline-block pull-right', => - @text "#{frame.path} @ #{frame.lineNumber}" + @text "#{frame.rawPath} @ #{frame.lineNumber}" @div class: 'panel-body padded', => @pre output: 'source', 'Source goes here' diff --git a/lib/stacktrace.coffee b/lib/stacktrace.coffee index cfa23fe..0895f7c 100644 --- a/lib/stacktrace.coffee +++ b/lib/stacktrace.coffee @@ -23,6 +23,11 @@ class Stacktrace # getUrl: -> @url ?= "#{PREFIX}/#{@getChecksum()}" + # Public: Determine whether or not this Stacktrace is the "active" one. The active Stacktrace is + # shown in a bottom navigation panel and highlighted in opened editors. + # + isActive: -> false + # Internal: Register this trace in a global map by its URL. # register: -> @@ -55,7 +60,8 @@ class Stacktrace # class Frame - constructor: (@rawLine, @path, @lineNumber, @functionName) -> + constructor: (@rawLine, @rawPath, @lineNumber, @functionName) -> + @realPath = @rawPath module.exports = PREFIX: PREFIX diff --git a/stylesheets/stacktrace.less b/stylesheets/stacktrace.less index 5bf0e72..12721f1 100644 --- a/stylesheets/stacktrace.less +++ b/stylesheets/stacktrace.less @@ -8,4 +8,21 @@ &.enter-dialog .editor { height: 300px; } + + .frame .panel-heading { + font-size: 130%; + + .source-location:hover { + cursor: pointer; + text-decoration: underline; + } + } + + &.activated { + .activate-control { display: none; } + } + + &.deactivated { + .deactivate-control { display: none; } + } } From f62810c34410bfcc99a685512f81b7b5a0579613 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 26 Jul 2014 18:00:16 -0400 Subject: [PATCH 06/22] Toggle each FrameView. --- lib/stacktrace-view.coffee | 31 ++++++++++++++++++++++++++++--- stylesheets/stacktrace.less | 13 +++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index c4c09bc..85edb1d 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -12,7 +12,7 @@ class StacktraceView extends View @button class: 'btn btn-primary selected inline-block', click: 'activate', 'Activate' @span class: 'inline-block', 'to navigate around this stacktrace.' @p class: 'deactivate-control', => - @button class: 'btn btn-primary inline-block', 'Deactivate' + @button class: 'btn btn-primary inline-block', click: 'deactivate', 'Deactivate' @span class: 'inline-block', 'to close the stacktrace navigation panel.' @div class: 'frames', => for frame in trace.frames @@ -21,11 +21,23 @@ class StacktraceView extends View initialize: (@trace) -> # Internal: Return the window title. + # getTitle: -> @trace.message + # Public: Activate the current {Stacktrace}. + # + activate: -> + console.log 'Activate' + + # Public: Deactivate the current {Stacktrace}. + # + deactivate: -> + console.log 'Deactivate' + # Internal: Register an opener function in the workspace to handle URLs # generated by a Stacktrace. + # @registerIn: (workspace) -> workspace.registerOpener (filePath) -> trace = Stacktrace.forUrl(filePath) @@ -37,14 +49,27 @@ class FrameView extends View @content: (frame) -> @div class: 'frame inset-panel', => @div class: 'panel-heading', => + @span class: 'icon icon-fold inline-block', click: 'minimize' + @span class: 'icon icon-unfold inline-block', click: 'restore' @span class: 'function-name text-highlight inline-block', frame.functionName - @span class: 'source-location text-info inline-block pull-right', => + @span class: 'source-location text-info inline-block pull-right', click: 'navigate', => @text "#{frame.rawPath} @ #{frame.lineNumber}" - @div class: 'panel-body padded', => + @div class: 'panel-body padded', outlet: 'body', => @pre output: 'source', 'Source goes here' initialize: (@frame) -> + navigate: -> + console.log "Navigate" + + minimize: -> + @addClass 'minimized' + @body.hide 'fast' + + restore: -> + @removeClass 'minimized' + @body.show 'fast' + module.exports = StacktraceView: StacktraceView FrameView: FrameView diff --git a/stylesheets/stacktrace.less b/stylesheets/stacktrace.less index 12721f1..5fb9d0c 100644 --- a/stylesheets/stacktrace.less +++ b/stylesheets/stacktrace.less @@ -18,6 +18,19 @@ } } + .frame { + .icon-fold, .icon-unfold { + cursor: pointer; + } + + .icon-unfold { display: none; } + + &.minimized { + .icon-unfold { display: inline-block; } + .icon-fold { display: none; } + } + } + &.activated { .activate-control { display: none; } } From 29586396f18a506188d9f0158ba712afab5dd43a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 26 Jul 2014 20:36:05 -0400 Subject: [PATCH 07/22] Oops, broke that test when I renamed path. --- spec/stacktrace-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/stacktrace-spec.coffee b/spec/stacktrace-spec.coffee index 889de8c..32590af 100644 --- a/spec/stacktrace-spec.coffee +++ b/spec/stacktrace-spec.coffee @@ -26,7 +26,7 @@ describe 'Stacktrace', -> expect(trace.message).toBe('whoops (RuntimeError)') it 'parses file paths from each frame', -> - filePaths = (frame.path for frame in trace.frames) + filePaths = (frame.realPath for frame in trace.frames) expected = [ '/home/smash/samples/tracer/otherdir/file2.rb' '/home/smash/samples/tracer/dir/file1.rb' From 8aadb50a1388d30e770f319385f3b73ccc761a8e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 08:34:39 -0400 Subject: [PATCH 08/22] Test case for fetching line context. --- lib/stacktrace-view.coffee | 2 +- spec/fixtures/context.txt | 11 +++++++++++ spec/stacktrace-spec.coffee | 25 ++++++++++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 spec/fixtures/context.txt diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index 85edb1d..83da43e 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -55,7 +55,7 @@ class FrameView extends View @span class: 'source-location text-info inline-block pull-right', click: 'navigate', => @text "#{frame.rawPath} @ #{frame.lineNumber}" @div class: 'panel-body padded', outlet: 'body', => - @pre output: 'source', 'Source goes here' + @pre outlet: 'source', 'Source goes here' initialize: (@frame) -> diff --git a/spec/fixtures/context.txt b/spec/fixtures/context.txt new file mode 100644 index 0000000..d6a16bf --- /dev/null +++ b/spec/fixtures/context.txt @@ -0,0 +1,11 @@ +zero +one +two +three +four +five +six +seven +eight +nine +ten diff --git a/spec/stacktrace-spec.coffee b/spec/stacktrace-spec.coffee index 32590af..f609873 100644 --- a/spec/stacktrace-spec.coffee +++ b/spec/stacktrace-spec.coffee @@ -1,5 +1,6 @@ -{Stacktrace} = require '../lib/stacktrace' +{Stacktrace, Frame} = require '../lib/stacktrace' {RUBY: {FUNCTION: TRACE}} = require './trace-fixtures' +require 'path' describe 'Stacktrace', -> describe 'with a Ruby trace', -> @@ -72,3 +73,25 @@ describe 'Stacktrace', -> expect(Stacktrace.forUrl(trace.getUrl())).toBe(trace) trace.unregister() expect(Stacktrace.forUrl(trace.getUrl())).toBeUndefined() + +describe 'Frame', -> + [frame] = [] + + beforeEach -> + fixturePath = path.join __dirname, 'fixtures', 'context.txt' + frame = new Frame('five', fixturePath, 5, 'something') + + it 'acquires n lines of context asynchronously', -> + lines = null + + frame.getContext 2, (ls) -> lines = ls + + waitsFor -> lines? + + runs -> + expect(lines.length).toBe(5) + expect(lines[0]).toEqual('three') + expect(lines[1]).toEqual('four') + expect(lines[2]).toEqual('five') + expect(lines[3]).toEqual('six') + expect(lines[4]).toEqual('seven') From d6d40418f40ca628ae0d95066d7790ec6db7b98e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 08:35:05 -0400 Subject: [PATCH 09/22] Use line-chomper to read files by lines. --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ee63d0d..6db1235 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "atom": ">0.50.0" }, "dependencies": { - "jssha": "^1.5.0" + "jssha": "^1.5.0", + "line-chomper": "^0.4.4" } } From 701598c7617d927318389e4ec620443daff57b6d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 08:47:06 -0400 Subject: [PATCH 10/22] Implement Frame.getContext(). --- lib/stacktrace.coffee | 17 ++++++++++++++++- spec/stacktrace-spec.coffee | 7 +++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/stacktrace.coffee b/lib/stacktrace.coffee index 0895f7c..ef6e32a 100644 --- a/lib/stacktrace.coffee +++ b/lib/stacktrace.coffee @@ -1,4 +1,6 @@ +fs = require 'fs' jsSHA = require 'jssha' +{chomp} = require 'line-chomper' traceParser = null PREFIX = 'stacktrace://trace' @@ -56,13 +58,26 @@ class Stacktrace @clearRegistry: -> REGISTRY = {} -# Internal: A single stack frame within a {Stacktrace}. +# Public: A single stack frame within a {Stacktrace}. # class Frame constructor: (@rawLine, @rawPath, @lineNumber, @functionName) -> @realPath = @rawPath + # Public: Asynchronously collect n lines of context around the specified line number in this + # frame's source file. + # + # n - The number of lines of context to collect on *each* side of the error line. The error + # line will always be `lines[n]` and `lines.length` will be `n * 2 + 1`. + # callback - Invoked with any errors or an Array containing the relevant lines. + # + getContext: (n, callback) -> + range = + fromLine: @lineNumber - n + toLine: @lineNumber + n + 1 + chomp fs.createReadStream(@realPath), range, callback + module.exports = PREFIX: PREFIX Stacktrace: Stacktrace diff --git a/spec/stacktrace-spec.coffee b/spec/stacktrace-spec.coffee index f609873..cfc97f5 100644 --- a/spec/stacktrace-spec.coffee +++ b/spec/stacktrace-spec.coffee @@ -1,6 +1,7 @@ +path = require 'path' + {Stacktrace, Frame} = require '../lib/stacktrace' {RUBY: {FUNCTION: TRACE}} = require './trace-fixtures' -require 'path' describe 'Stacktrace', -> describe 'with a Ruby trace', -> @@ -84,7 +85,9 @@ describe 'Frame', -> it 'acquires n lines of context asynchronously', -> lines = null - frame.getContext 2, (ls) -> lines = ls + frame.getContext 2, (err, ls) -> + throw err if err? + lines = ls waitsFor -> lines? From b306d95ef68a576752c88ddc4b23eca986fd8642 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 09:30:10 -0400 Subject: [PATCH 11/22] Use my line-chomper fork. So I can disable the automatic `.trim()`. --- lib/stacktrace.coffee | 2 ++ package.json | 2 +- spec/fixtures/context.txt | 4 ++-- spec/stacktrace-spec.coffee | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/stacktrace.coffee b/lib/stacktrace.coffee index ef6e32a..f14a9a3 100644 --- a/lib/stacktrace.coffee +++ b/lib/stacktrace.coffee @@ -76,6 +76,8 @@ class Frame range = fromLine: @lineNumber - n toLine: @lineNumber + n + 1 + trim: false + keepLastEmptyLine: true chomp fs.createReadStream(@realPath), range, callback module.exports = diff --git a/package.json b/package.json index 6db1235..6f45385 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,6 @@ }, "dependencies": { "jssha": "^1.5.0", - "line-chomper": "^0.4.4" + "line-chomper": "git+https://github.com/smashwilson/line-chomper.git#optional-trim" } } diff --git a/spec/fixtures/context.txt b/spec/fixtures/context.txt index d6a16bf..b02e70e 100644 --- a/spec/fixtures/context.txt +++ b/spec/fixtures/context.txt @@ -2,10 +2,10 @@ zero one two three -four + four five six -seven + eight nine ten diff --git a/spec/stacktrace-spec.coffee b/spec/stacktrace-spec.coffee index cfc97f5..43865c2 100644 --- a/spec/stacktrace-spec.coffee +++ b/spec/stacktrace-spec.coffee @@ -94,7 +94,7 @@ describe 'Frame', -> runs -> expect(lines.length).toBe(5) expect(lines[0]).toEqual('three') - expect(lines[1]).toEqual('four') + expect(lines[1]).toEqual(' four') expect(lines[2]).toEqual('five') expect(lines[3]).toEqual('six') - expect(lines[4]).toEqual('seven') + expect(lines[4]).toEqual('') From 840d8630dc85509915d00299be8ae77d34e258e7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 09:32:03 -0400 Subject: [PATCH 12/22] Use a mini-EditorView to show code snippets. --- lib/stacktrace-view.coffee | 9 +++++++-- stylesheets/stacktrace.less | 2 ++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index 83da43e..5ce705a 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -1,4 +1,4 @@ -{View} = require 'atom' +{View, EditorView} = require 'atom' {Stacktrace, PREFIX} = require './stacktrace' class StacktraceView extends View @@ -55,9 +55,14 @@ class FrameView extends View @span class: 'source-location text-info inline-block pull-right', click: 'navigate', => @text "#{frame.rawPath} @ #{frame.lineNumber}" @div class: 'panel-body padded', outlet: 'body', => - @pre outlet: 'source', 'Source goes here' + @subview 'source', new EditorView(mini: true) initialize: (@frame) -> + @frame.getContext 3, (err, lines) => + if err? + console.error err + else + @source.getEditor().setText lines.join("\n") navigate: -> console.log "Navigate" diff --git a/stylesheets/stacktrace.less b/stylesheets/stacktrace.less index 5fb9d0c..ea41167 100644 --- a/stylesheets/stacktrace.less +++ b/stylesheets/stacktrace.less @@ -19,6 +19,8 @@ } .frame { + margin-bottom: 10px; + .icon-fold, .icon-unfold { cursor: pointer; } From 70d5e3bb5f08c9c2c2a09f6105c3427937471c2a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 09:37:53 -0400 Subject: [PATCH 13/22] Hey look an off-by-one error --- lib/stacktrace.coffee | 5 +++-- spec/fixtures/context.txt | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/stacktrace.coffee b/lib/stacktrace.coffee index f14a9a3..861a7b4 100644 --- a/lib/stacktrace.coffee +++ b/lib/stacktrace.coffee @@ -73,9 +73,10 @@ class Frame # callback - Invoked with any errors or an Array containing the relevant lines. # getContext: (n, callback) -> + # Notice that @lineNumber is one-indexed, not zero-indexed. range = - fromLine: @lineNumber - n - toLine: @lineNumber + n + 1 + fromLine: @lineNumber - n - 1 + toLine: @lineNumber + n trim: false keepLastEmptyLine: true chomp fs.createReadStream(@realPath), range, callback diff --git a/spec/fixtures/context.txt b/spec/fixtures/context.txt index b02e70e..5a3ecb4 100644 --- a/spec/fixtures/context.txt +++ b/spec/fixtures/context.txt @@ -1,4 +1,3 @@ -zero one two three From 3bf2b3cbca2e3197511887fafd74472f0ed95f47 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 11:01:48 -0400 Subject: [PATCH 14/22] Navigate to a specific Frame. --- lib/stacktrace-view.coffee | 3 +-- lib/stacktrace.coffee | 11 +++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index 5ce705a..df00dbc 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -64,8 +64,7 @@ class FrameView extends View else @source.getEditor().setText lines.join("\n") - navigate: -> - console.log "Navigate" + navigate: -> @frame.navigateTo() minimize: -> @addClass 'minimized' diff --git a/lib/stacktrace.coffee b/lib/stacktrace.coffee index 861a7b4..75e9356 100644 --- a/lib/stacktrace.coffee +++ b/lib/stacktrace.coffee @@ -81,6 +81,17 @@ class Frame keepLastEmptyLine: true chomp fs.createReadStream(@realPath), range, callback + navigateTo: -> + position = [@lineNumber - 1, 0] + promise = atom.workspace.open @realPath, initialLine: position[0] + promise.then (editor) -> + editor.setCursorBufferPosition position + for ev in atom.workspaceView.getEditorViews() + editorView = ev if ev.getEditor() is editor + if editorView? + editorView.scrollToBufferPosition position, center: true + + module.exports = PREFIX: PREFIX Stacktrace: Stacktrace From 267140a1befd028d2bed947cefadff3208584c9e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 11:04:46 -0400 Subject: [PATCH 15/22] Navigate on a click anywhere in the frame. I'm not so sure about this one. --- lib/stacktrace-view.coffee | 2 +- stylesheets/stacktrace.less | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index df00dbc..ad25767 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -54,7 +54,7 @@ class FrameView extends View @span class: 'function-name text-highlight inline-block', frame.functionName @span class: 'source-location text-info inline-block pull-right', click: 'navigate', => @text "#{frame.rawPath} @ #{frame.lineNumber}" - @div class: 'panel-body padded', outlet: 'body', => + @div class: 'panel-body padded', outlet: 'body', click: 'navigate', => @subview 'source', new EditorView(mini: true) initialize: (@frame) -> diff --git a/stylesheets/stacktrace.less b/stylesheets/stacktrace.less index ea41167..6e1f365 100644 --- a/stylesheets/stacktrace.less +++ b/stylesheets/stacktrace.less @@ -31,6 +31,10 @@ .icon-unfold { display: inline-block; } .icon-fold { display: none; } } + + .editor { + cursor: pointer; + } } &.activated { From c7f9779d893a868bae86f5156770070d35bc3aa0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 11:11:56 -0400 Subject: [PATCH 16/22] Mark the currently "active" Stacktrace. --- lib/stacktrace.coffee | 14 ++++++++++++++ spec/stacktrace-spec.coffee | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/stacktrace.coffee b/lib/stacktrace.coffee index 75e9356..8cfe6ad 100644 --- a/lib/stacktrace.coffee +++ b/lib/stacktrace.coffee @@ -6,6 +6,7 @@ traceParser = null PREFIX = 'stacktrace://trace' REGISTRY = {} +ACTIVE = null # Internal: A heuristically parsed and interpreted stacktrace. # @@ -41,6 +42,15 @@ class Stacktrace unregister: -> delete REGISTRY[@getUrl()] + # Public: Mark this trace as the "active" one. The active trace is shown in the navigation view + # and its frames are given a marker in an open {EditorView}. + # + activate: -> ACTIVE = this + + # Public: Deactivate this trace if it's active. + # + deactivate: -> ACTIVE = null if ACTIVE is this + # Public: Parse zero to many Stacktrace instances from a corpus of text. # # text - A raw blob of text. @@ -58,6 +68,10 @@ class Stacktrace @clearRegistry: -> REGISTRY = {} + # Public: Retrieve the currently activated {Stacktrace}, or null if no trace is active. + # + @getActivated: -> ACTIVE + # Public: A single stack frame within a {Stacktrace}. # class Frame diff --git a/spec/stacktrace-spec.coffee b/spec/stacktrace-spec.coffee index 43865c2..c500118 100644 --- a/spec/stacktrace-spec.coffee +++ b/spec/stacktrace-spec.coffee @@ -75,6 +75,24 @@ describe 'Stacktrace', -> trace.unregister() expect(Stacktrace.forUrl(trace.getUrl())).toBeUndefined() + describe 'activation', -> + afterEach -> + activated = Stacktrace.getActivated() + activated.deactivate() if activated? + + it 'can be activated', -> + trace.activate() + expect(Stacktrace.getActivated()).toBe(trace) + + it 'can be deactivated if activated', -> + trace.activate() + trace.deactivate() + expect(Stacktrace.getActivated()).toBeNull() + + it 'can be deactivated even if not activated', -> + trace.deactivate() + expect(Stacktrace.getActivated()).toBeNull() + describe 'Frame', -> [frame] = [] From 6f338d56629a2670d51e49c45780600395eb9df2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 11:13:16 -0400 Subject: [PATCH 17/22] Wire up the activate and deactivate events. --- lib/stacktrace-view.coffee | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index ad25767..85e178d 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -27,13 +27,11 @@ class StacktraceView extends View # Public: Activate the current {Stacktrace}. # - activate: -> - console.log 'Activate' + activate: -> @trace.activate() # Public: Deactivate the current {Stacktrace}. # - deactivate: -> - console.log 'Deactivate' + deactivate: -> @trace.deactivate() # Internal: Register an opener function in the workspace to handle URLs # generated by a Stacktrace. From b7de2992c8364ab568e3fcf7afb065cb0dbe4e5d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 11:18:25 -0400 Subject: [PATCH 18/22] Depend on Emissary for event stuff. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 6f45385..794962d 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "atom": ">0.50.0" }, "dependencies": { + "emissary": "^1.2.1", "jssha": "^1.5.0", "line-chomper": "git+https://github.com/smashwilson/line-chomper.git#optional-trim" } From 28b8eaec96e45abf83225362d77aa46b5814bdb7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 11:20:53 -0400 Subject: [PATCH 19/22] Emit events when the active Stacktrace is changed. --- lib/stacktrace.coffee | 17 +++++++++++++++-- spec/stacktrace-spec.coffee | 9 +++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/stacktrace.coffee b/lib/stacktrace.coffee index 8cfe6ad..413f38e 100644 --- a/lib/stacktrace.coffee +++ b/lib/stacktrace.coffee @@ -1,4 +1,7 @@ fs = require 'fs' + +{Emitter} = require 'emissary' + jsSHA = require 'jssha' {chomp} = require 'line-chomper' traceParser = null @@ -12,6 +15,9 @@ ACTIVE = null # class Stacktrace + # Turn the Stacktrace class into an emitter. + Emitter.extend this + constructor: (@frames = [], @message = '') -> # Internal: Compute the SHA256 checksum of the normalized stacktrace. @@ -45,11 +51,18 @@ class Stacktrace # Public: Mark this trace as the "active" one. The active trace is shown in the navigation view # and its frames are given a marker in an open {EditorView}. # - activate: -> ACTIVE = this + activate: -> + former = ACTIVE + ACTIVE = this + if former isnt ACTIVE + Stacktrace.emit 'active-changed', oldTrace: former, newTrace: ACTIVE # Public: Deactivate this trace if it's active. # - deactivate: -> ACTIVE = null if ACTIVE is this + deactivate: -> + if ACTIVE is this + ACTIVE = null + Stacktrace.emit 'active-changed', oldTrace: this, newTrace: null # Public: Parse zero to many Stacktrace instances from a corpus of text. # diff --git a/spec/stacktrace-spec.coffee b/spec/stacktrace-spec.coffee index c500118..f6cd82e 100644 --- a/spec/stacktrace-spec.coffee +++ b/spec/stacktrace-spec.coffee @@ -79,6 +79,7 @@ describe 'Stacktrace', -> afterEach -> activated = Stacktrace.getActivated() activated.deactivate() if activated? + Stacktrace.off 'active-changed' it 'can be activated', -> trace.activate() @@ -93,6 +94,14 @@ describe 'Stacktrace', -> trace.deactivate() expect(Stacktrace.getActivated()).toBeNull() + it 'broadcasts a "active-changed" event', -> + event = null + Stacktrace.on 'active-changed', (e) -> event = e + + trace.activate() + expect(event.oldTrace).toBeNull() + expect(event.newTrace).toBe(trace) + describe 'Frame', -> [frame] = [] From 1850e30a6712ce10db6ace8e9b00602a1dd2ca6a Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 11:32:18 -0400 Subject: [PATCH 20/22] Toggle the StacktraceView class on activation. --- lib/stacktrace-view.coffee | 11 ++++++++++- spec/stacktrace-view-spec.coffee | 6 ++++++ stylesheets/stacktrace.less | 6 +++--- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index 85e178d..92d5d97 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -1,10 +1,14 @@ {View, EditorView} = require 'atom' +{Subscriber} = require 'emissary' + {Stacktrace, PREFIX} = require './stacktrace' class StacktraceView extends View + Subscriber.includeInto this + @content: (trace) -> - tclass = if trace.isActive() then 'activated' else 'deactivated' + tclass = if trace.isActive() then 'activated' else '' @div class: "stacktrace tool-panel padded #{tclass}", => @div class: 'panel padded', => @h2 class: 'error-message', trace.message @@ -19,6 +23,11 @@ class StacktraceView extends View @subview 'frame', new FrameView(frame) initialize: (@trace) -> + @subscribe Stacktrace, 'active-changed', (e) => + if e.newTrace is @trace + @addClass 'activated' + else + @removeClass 'activated' # Internal: Return the window title. # diff --git a/spec/stacktrace-view-spec.coffee b/spec/stacktrace-view-spec.coffee index e726f4f..83b7fa3 100644 --- a/spec/stacktrace-view-spec.coffee +++ b/spec/stacktrace-view-spec.coffee @@ -37,6 +37,12 @@ describe 'StacktraceView', -> vs = view.find('.frame') expect(vs.length).toBe(3) + it 'changes its class when its trace is activated or deactivated', -> + Stacktrace.getActivated()?.deactivate() + expect(view.hasClass 'activated').toBe(false) + trace.activate() + expect(view.hasClass 'activated').toBe(true) + describe 'FrameView', -> [view] = [] diff --git a/stylesheets/stacktrace.less b/stylesheets/stacktrace.less index 6e1f365..282fe3c 100644 --- a/stylesheets/stacktrace.less +++ b/stylesheets/stacktrace.less @@ -37,11 +37,11 @@ } } + .deactivate-control { display: none; } + &.activated { + .deactivate-control { display: block; } .activate-control { display: none; } } - &.deactivated { - .deactivate-control { display: none; } - } } From 1190bcec5429516d13299d62dff01d4f65249c7c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 11:39:53 -0400 Subject: [PATCH 21/22] Activate the Stacktrace on navigation. --- lib/stacktrace-view.coffee | 10 ++++++---- spec/stacktrace-view-spec.coffee | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index 92d5d97..db0dc7c 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -20,7 +20,7 @@ class StacktraceView extends View @span class: 'inline-block', 'to close the stacktrace navigation panel.' @div class: 'frames', => for frame in trace.frames - @subview 'frame', new FrameView(frame) + @subview 'frame', new FrameView frame, => trace.activate() initialize: (@trace) -> @subscribe Stacktrace, 'active-changed', (e) => @@ -53,7 +53,7 @@ class StacktraceView extends View class FrameView extends View - @content: (frame) -> + @content: (frame, navCallback) -> @div class: 'frame inset-panel', => @div class: 'panel-heading', => @span class: 'icon icon-fold inline-block', click: 'minimize' @@ -64,14 +64,16 @@ class FrameView extends View @div class: 'panel-body padded', outlet: 'body', click: 'navigate', => @subview 'source', new EditorView(mini: true) - initialize: (@frame) -> + initialize: (@frame, @navCallback) -> @frame.getContext 3, (err, lines) => if err? console.error err else @source.getEditor().setText lines.join("\n") - navigate: -> @frame.navigateTo() + navigate: -> + @navCallback() + @frame.navigateTo() minimize: -> @addClass 'minimized' diff --git a/spec/stacktrace-view-spec.coffee b/spec/stacktrace-view-spec.coffee index 83b7fa3..c6895fd 100644 --- a/spec/stacktrace-view-spec.coffee +++ b/spec/stacktrace-view-spec.coffee @@ -47,7 +47,7 @@ describe 'FrameView', -> [view] = [] beforeEach -> - view = new FrameView(frames[1]) + view = new FrameView frames[1], -> it 'shows the filename and line number', -> text = view.find('.source-location').text() From 12db0695f797895f3b0aba6b27f86ed75a3df2e3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sun, 27 Jul 2014 11:41:47 -0400 Subject: [PATCH 22/22] Unsubscribe on view close. --- lib/stacktrace-view.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/stacktrace-view.coffee b/lib/stacktrace-view.coffee index db0dc7c..bda2d1f 100644 --- a/lib/stacktrace-view.coffee +++ b/lib/stacktrace-view.coffee @@ -29,6 +29,9 @@ class StacktraceView extends View else @removeClass 'activated' + beforeRemove: -> + @unsubscribe Stacktrace + # Internal: Return the window title. # getTitle: ->