From 066b1798abcca78f0a9fdb7d7570ea396bc88697 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Sun, 30 Nov 2014 11:47:51 -0800 Subject: [PATCH] Make default code read-only --- app/lib/CampaignOptions.coffee | 2 + app/lib/LevelOptions.coffee | 2 + app/views/play/level/tome/SpellView.coffee | 80 +++++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/app/lib/CampaignOptions.coffee b/app/lib/CampaignOptions.coffee index 8fb64858a..719085bcb 100644 --- a/app/lib/CampaignOptions.coffee +++ b/app/lib/CampaignOptions.coffee @@ -7,9 +7,11 @@ options = 'default': autocompleteFontSizePx: 16 backspaceThrottle: false + lockDefaultCode: false 'dungeon': autocompleteFontSizePx: 20 backspaceThrottle: true + lockDefaultCode: true module.exports = CampaignOptions = getCampaignForSlug: (slug) -> diff --git a/app/lib/LevelOptions.coffee b/app/lib/LevelOptions.coffee index 6e16e06d7..7d020793d 100644 --- a/app/lib/LevelOptions.coffee +++ b/app/lib/LevelOptions.coffee @@ -214,10 +214,12 @@ module.exports = LevelOptions = restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-i'} 'village-guard': hidesCodeToolbar: true + lockDefaultCode: true requiredGear: {feet: 'leather-boots', 'right-hand': 'simple-sword', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'} restrictedGear: {feet: 'simple-boots', 'right-hand': 'crude-builders-hammer'} 'thornbush-farm': hidesCodeToolbar: true + lockDefaultCode: true requiredGear: {feet: 'leather-boots', 'right-hand': 'crude-builders-hammer', 'programming-book': 'programmaticon-ii', eyes: 'crude-glasses'} restrictedGear: {feet: 'simple-boots', 'right-hand': 'simple-sword'} requiredCode: ['topEnemy'] diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee index 7ac85ef66..185fa5f3a 100644 --- a/app/views/play/level/tome/SpellView.coffee +++ b/app/views/play/level/tome/SpellView.coffee @@ -77,6 +77,7 @@ module.exports = class SpellView extends CocoView @createACE() @createACEShortcuts() @fillACE() + @lockDefaultCode() if @session.get('multiplayer') @createFirepad() else @@ -142,7 +143,6 @@ module.exports = class SpellView extends CocoView Backbone.Mediator.publish 'level:shift-space-pressed', {} else @ace.insert ' ' - addCommand name: 'end-all-scripts' bindKey: {win: 'Escape', mac: 'Escape'} @@ -253,6 +253,83 @@ module.exports = class SpellView extends CocoView @aceSession.setUndoManager(new UndoManager()) @ace.clearSelection() + lockDefaultCode: (force=false) -> + # TODO: Lock default indent for an empty line? + return unless LevelOptions[@options.level.get('slug')]?.lockDefaultCode or CampaignOptions?.getOption?(@options?.level?.get?('slug'), 'lockDefaultCode') + return unless @spell.source is @spell.originalSource or force + + console.info 'Locking down default code.' + + intersects = => + return true for range in @readOnlyRanges when @ace.getSelectionRange().intersects(range) + false + + intersectsLeft = => + leftRange = @ace.getSelectionRange().clone() + if leftRange.start.column > 0 + leftRange.setStart leftRange.start.row, leftRange.start.column - 1 + else if leftRange.start.row > 0 + leftRange.setStart leftRange.start.row - 1, 0 + return true for range in @readOnlyRanges when leftRange.intersects(range) + false + + preventReadonly = (next) -> + return true if intersects() + next?() + + interceptCommand = (obj, method, wrapper) -> + orig = obj[method] + obj[method] = -> + args = Array.prototype.slice.call arguments + wrapper => orig.apply obj, args + obj[method] + + finishRange = (row, startRow, startColumn) => + range = new Range startRow, startColumn, row, @aceSession.getLine(row).length - 1 + range.start = @aceDoc.createAnchor range.start + range.end = @aceDoc.createAnchor range.end + range.end.$insertRight = true + @readOnlyRanges.push range + + # Create a read-only range for each chunk of text not separated by an empty line + @readOnlyRanges = [] + startRow = startColumn = null + for row in [0...@aceSession.getLength()] + unless /^\s*$/.test @aceSession.getLine(row) + unless startRow? and startColumn? + startRow = row + startColumn = 0 + else + if startRow? and startColumn? + finishRange row - 1, startRow, startColumn + startRow = startColumn = null + if startRow? and startColumn? + finishRange @aceSession.getLength() - 1, startRow, startColumn + + # Override write operations that intersect with default code + interceptCommand @ace, 'onPaste', preventReadonly + interceptCommand @ace, 'onCut', preventReadonly + # TODO: can we use interceptCommand for this too? 'exec' and 'onExec' did not work. + @ace.commands.on 'exec', (e) => + e.stopPropagation() + e.preventDefault() + if e.command.name is 'insertstring'and intersects() + @zatanna?.off?() + return false + if e.command.name in ['Backspace', 'throttle-backspaces'] and intersectsLeft() + @zatanna?.off?() + return false + if e.command.name in ['enter-skip-delimiters', 'Enter', 'Return'] + if intersects() + @ace.navigateDown 1 + @ace.navigateLineStart() + return false + else if e.command.name in ['Enter', 'Return'] + @ace.execCommand 'enter-skip-delimiters' + return false + @zatanna?.on?() + e.command.exec e.editor, e.args or {} + initAutocomplete: (@autocomplete) -> # TODO: Turn on more autocompletion based on level sophistication # TODO: E.g. using the language default snippets yields a bunch of crazy non-beginner suggestions @@ -450,6 +527,7 @@ module.exports = class SpellView extends CocoView reloadCode: (cast=true) -> @updateACEText @spell.originalSource + @lockDefaultCode true @recompile cast Backbone.Mediator.publish 'tome:spell-loaded', spell: @spell