codecombat/app/views/play/level/tome/Problem.coffee

104 lines
4.5 KiB
CoffeeScript
Raw Normal View History

2014-06-30 22:16:26 -04:00
Range = ace.require('ace/range').Range
2014-01-03 13:32:13 -05:00
# This class can either wrap an AetherProblem,
# or act as a general runtime error container for web-dev iFrame errors.
# TODO: Use subclasses? Might need a factory pattern for that (bleh)
2014-01-03 13:32:13 -05:00
module.exports = class Problem
annotation: null
markerRange: null
# Construction with AetherProblem will include all but `error`
# Construction with a standard error will have `error`, `isCast`, `levelID`, `ace`
constructor: ({ @aether, @aetherProblem, @ace, isCast=false, @levelID, error, userCodeHasChangedSinceLastCast }) ->
if @aetherProblem
@annotation = @buildAnnotationFromAetherProblem(@aetherProblem)
{ @lineMarkerRange, @textMarkerRange } = @buildMarkerRangesFromAetherProblem(@aetherProblem) if isCast
{ @level, @range, @message, @hint, @userInfo } = @aetherProblem
{ @row, @column: col } = @aetherProblem.range?[0]
@createdBy = 'aether'
else
unless userCodeHasChangedSinceLastCast
@annotation = @buildAnnotationFromWebDevError(error)
{ @lineMarkerRange, @textMarkerRange } = @buildMarkerRangesFromWebDevError(error)
@level = 'error'
@row = error.line
@column = error.column
@message = error.message or 'Unknown Error'
if error.line and not userCodeHasChangedSinceLastCast
@message = "Line #{error.line + 1}: " + @message # Ace's gutter numbers are 1-indexed but annotation.rows are 0-indexed
if userCodeHasChangedSinceLastCast
@hint = "This error was generated by old code — Try running your new code first."
else
@hint = undefined
@userInfo = undefined
@createdBy = 'web-dev-iframe'
# TODO: Include runtime/transpile error types depending on something?
# TODO: get ACE screen line, too, for positioning, since any multiline "lines" will mess up positioning
Backbone.Mediator.publish("problem:problem-created", line: @annotation.row, text: @annotation.text) if application.isIPadApp
2014-01-03 13:32:13 -05:00
isEqual: (problem) ->
_.all ['row', 'column', 'level', 'column', 'message', 'hint'], (attr) =>
@[attr] is problem[attr]
2014-01-03 13:32:13 -05:00
destroy: ->
@removeMarkerRanges()
2014-08-14 14:55:43 -04:00
@userCodeProblem.off() if @userCodeProblem
2014-01-03 13:32:13 -05:00
buildAnnotationFromWebDevError: (error) ->
{
row: error.line
column: error.column
raw: error.message
text: error.message
type: 'error'
createdBy: 'web-dev-iframe'
}
buildAnnotationFromAetherProblem: (aetherProblem) ->
return unless aetherProblem.range
text = aetherProblem.message.replace /^Line \d+: /, ''
start = aetherProblem.range[0]
{
row: start.row,
column: start.col,
2014-01-03 13:32:13 -05:00
raw: text,
text: text,
2014-06-30 22:16:26 -04:00
type: @aetherProblem.level ? 'error'
createdBy: 'aether'
}
buildMarkerRangesFromWebDevError: (error) ->
lineMarkerRange = new Range error.line, 0, error.line, 1
lineMarkerRange.start = @ace.getSession().getDocument().createAnchor lineMarkerRange.start
lineMarkerRange.end = @ace.getSession().getDocument().createAnchor lineMarkerRange.end
lineMarkerRange.id = @ace.getSession().addMarker lineMarkerRange, 'problem-line', 'fullLine'
textMarkerRange = undefined # We don't get any per-character info from standard errors
{ lineMarkerRange, textMarkerRange }
2014-01-03 13:32:13 -05:00
buildMarkerRangesFromAetherProblem: (aetherProblem) ->
return {} unless aetherProblem.range
[start, end] = aetherProblem.range
textClazz = "problem-marker-#{aetherProblem.level}"
textMarkerRange = new Range start.row, start.col, end.row, end.col
textMarkerRange.start = @ace.getSession().getDocument().createAnchor textMarkerRange.start
textMarkerRange.end = @ace.getSession().getDocument().createAnchor textMarkerRange.end
textMarkerRange.id = @ace.getSession().addMarker textMarkerRange, textClazz, 'text'
lineClazz = "problem-line"
lineMarkerRange = new Range start.row, start.col, end.row, end.col
lineMarkerRange.start = @ace.getSession().getDocument().createAnchor lineMarkerRange.start
lineMarkerRange.end = @ace.getSession().getDocument().createAnchor lineMarkerRange.end
lineMarkerRange.id = @ace.getSession().addMarker lineMarkerRange, lineClazz, 'fullLine'
{ lineMarkerRange, textMarkerRange }
2014-01-03 13:32:13 -05:00
removeMarkerRanges: ->
if @textMarkerRange
@ace.getSession().removeMarker @textMarkerRange.id
@textMarkerRange.start.detach()
@textMarkerRange.end.detach()
if @lineMarkerRange
@ace.getSession().removeMarker @lineMarkerRange.id
@lineMarkerRange.start.detach()
@lineMarkerRange.end.detach()