mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-01-23 04:39:49 -05:00
663c220eaf
This heavily refactors SpellView and adds infrastructure for receiving and reporting Errors raised by the web-dev iFrame. The web-dev error system, the Aether error system, and the Ace html-worker avoid disturbing each others' errors/annotations (though currently Aether+web-dev errors won't coexist), and they clear/update their own asynchronously. Show web-dev iFrame errors as Ace annotations Add functional error banners (with poor messages) Improve error banners, don't allow duplicate Problems Refactor setAnnotations override Convert all constructor calls for Problems Add comments, clean up Clean up Don't clear things unnecessarily Clean up error message sending from iFrame Add web-dev:error schema Clarify error message attributes Refactor displaying AetherProblems Refactor displaying user problem banners Refactor onWebDevError Set ace styles on updating @problems Clean up, fix off-by-1 error Add comment Show stale web-dev errors differently Some web-dev errors are generated by "stale" code — code that's still running in the iFrame but doesn't have the player's recent changes. This shows those errors differently than if they weren't "stale", and suggests they re-run their code. Hook up web-dev event schema Destroy ignored duplicate problems Functionalize a bit of stuff Fix ProblemAlertView never loading
103 lines
4.5 KiB
CoffeeScript
103 lines
4.5 KiB
CoffeeScript
Range = ace.require('ace/range').Range
|
||
|
||
# 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)
|
||
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
|
||
|
||
isEqual: (problem) ->
|
||
_.all ['row', 'column', 'level', 'column', 'message', 'hint'], (attr) =>
|
||
@[attr] is problem[attr]
|
||
|
||
destroy: ->
|
||
@removeMarkerRanges()
|
||
@userCodeProblem.off() if @userCodeProblem
|
||
|
||
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,
|
||
raw: text,
|
||
text: text,
|
||
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 }
|
||
|
||
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 }
|
||
|
||
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()
|