diff --git a/src/coffee.coffee b/src/coffee.coffee index 77d2ca90..1b3ef43a 100644 --- a/src/coffee.coffee +++ b/src/coffee.coffee @@ -1,4 +1,4 @@ -# # ICE Editor CoffeeScript mode +## ICE Editor CoffeeScript mode # # Copyright (c) Anthony Bau # MIT License @@ -11,6 +11,9 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( BLOCK_ONLY = ['block-only'] MOSTLY_BLOCK = ['mostly-block'] MOSTLY_VALUE = ['mostly-value'] + LIST_WRAPPER = ['list'] + OBJ_WRAPPER = ['object', 'list'] + BLANK_BLOCK = ['blank-block'] VALUE_ONLY = ['value-only'] LVALUE = ['lvalue'] FORBID_ALL = ['forbid-all'] @@ -256,6 +259,24 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( @csSocketAndMark param, depth, 0, indentDepth, FORBID_ALL @mark node.body, depth, 0, null, indentDepth + checkShouldBeOneLine: (node) -> + bounds = @getBounds node + + # See if we want to wrap in a socket + # rather than an indent. + shouldBeOneLine = false + + # Check to see if any parent node is occupying a line + # we are on. If so, we probably want to wrap in + # a socket rather than an indent. + for line in [bounds.start.line..bounds.end.line] + shouldBeOneLine or= @hasLineBeenMarked[line] + + if @lines[bounds.start.line][...bounds.start.column].trim().length isnt 0 + shouldBeOneLine = true + + return shouldBeOneLine + # ## mark ## # Mark a single node. The main recursive function. mark: (node, depth, precedence, wrappingParen, indentDepth) -> @@ -273,18 +294,7 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( # whether we want to do it on one line or multiple lines. bounds = @getBounds node - # See if we want to wrap in a socket - # rather than an indent. - shouldBeOneLine = false - - # Check to see if any parent node is occupying a line - # we are on. If so, we probably want to wrap in - # a socket rather than an indent. - for line in [bounds.start.line..bounds.end.line] - shouldBeOneLine or= @hasLineBeenMarked[line] - - if @lines[bounds.start.line][...bounds.start.column].trim().length isnt 0 - shouldBeOneLine = true + shouldBeOneLine = @checkShouldBeOneLine node if shouldBeOneLine @csSocket node, depth, 0 @@ -499,7 +509,7 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( if node.value.nodeType() is 'Code' @addCode node.value, depth + 1, indentDepth else - @csSocketAndMark node.value, depth + 1, 0, indentDepth + @csSocketAndMark node.value, depth + 1, -1, indentDepth # ### For ### # Color CONTROL, options sockets @index, @source, @name, @from. @@ -571,11 +581,21 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( @csBlock node, depth, 100, 'purple', wrappingParen, VALUE_ONLY if node.objects.length > 0 - @csIndentAndMark indentDepth, node.objects, depth + 1 + @csIndentAndMark indentDepth, node.objects, depth + 1, LIST_WRAPPER for object in node.objects - if object.nodeType() is 'Value' and object.base.nodeType() is 'Literal' and - object.properties?.length in [0, undefined] - @csBlock object, depth + 2, 100, 'return', null, VALUE_ONLY + subject = @getParenBase(object) + if subject.nodeType() is 'Value' and subject.base.nodeType() is 'Literal' and + subject.properties?.length in [0, undefined] + @csBlock object, depth + 2, 100, 'blank', null, BLANK_BLOCK + + # See if there is a comma after this object + bounds = @getBounds object + bounds.end.column -= /,\s*$/.exec(@lines[bounds.end.line][...bounds.end.column])?[0]?.length ? 0 + @addSocket + bounds: bounds + depth: depth + 3 + precedence: 100 + classes: [] # ### Return ### # Color RETURN, optional socket @expression. @@ -628,12 +648,8 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( # maybe our View architecture is wrong. when 'Obj' @csBlock node, depth, 0, 'purple', wrappingParen, VALUE_ONLY - - for property in node.properties - if property.nodeType() is 'Assign' - @csSocketAndMark property.variable, depth + 1, 0, indentDepth, FORBID_ALL - @csSocketAndMark property.value, depth + 1, 0, indentDepth - + if node.properties.length > 0 + @csIndentAndMark indentDepth, node.properties, depth + 1, OBJ_WRAPPER locationsAreIdentical: (a, b) -> return a.line is b.line and a.column is b.column @@ -650,6 +666,14 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( else if a.column < b.column then b else a + getParenBase: (node) -> + while node.nodeType() is 'Value' and + node.base?.nodeType() is 'Parens' and + node.properties?.length in [0, undefined] and + node.base.body.expressions[0]?.nodeType() is 'Value' + node = node.base.body.expressions[0] + return node + # ## getBounds ## # Get the boundary locations of a CoffeeScript node, # using CoffeeScript location data and @@ -677,6 +701,7 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( # of the last one if node.expressions.length > 0 bounds.end = @getBounds(node.expressions[node.expressions.length - 1]).end + bounds.start = @boundMin bounds.start, @getBounds(node.expressions[0]).start #If we have no child expressions, make the bounds actually empty. else @@ -714,6 +739,9 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( bounds.end.line -= 1 bounds.end.column = @lines[bounds.end.line].length + 1 + if node.nodeType() is 'Obj' and node.properties.length > 0 + bounds.start = @boundMin bounds.start, @getWrappingBounds(node.properties[0], node.properties[node.properties.length - 1]).start + # When we have a 'Value' object, # its base may have some exceptions in it, # in which case we want to pass on to @@ -762,16 +790,25 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( parenWrapped: wrappingParen? } - # Add an indent node and guess - # at the indent depth - csIndent: (indentDepth, firstNode, lastNode, depth) -> + getWrappingBounds: (firstNode, lastNode) -> + if not firstNode? or not lastNode? + console.log 'err on', firstNode, firstNode?.locationData, lastNode, lastNode?.locationData first = @getBounds(firstNode).start last = @getBounds(lastNode).end - if @lines[first.line][...first.column].trim().length is 0 + if first.line > 0 and @lines[first.line][...first.column].trim().length is 0 first.line -= 1 - first.column = @lines[first.line].length + first.column = @lines[first.line].length + 1 + return { + start: first + end: last + } + + # Add an indent node and guess + # at the indent depth + csIndent: (indentDepth, firstNode, lastNode, depth, classes = []) -> + {start: first, end: last} = @getWrappingBounds firstNode, lastNode if first.line isnt last.line trueDepth = @lines[last.line].length - @lines[last.line].trimLeft().length prefix = @lines[last.line][indentDepth...trueDepth] @@ -780,19 +817,16 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( prefix = ' ' @addIndent { - bounds: { - start: first - end: last - } + bounds: {start: first, end: last} depth: depth - + classes: classes prefix: prefix } return trueDepth - csIndentAndMark: (indentDepth, nodes, depth) -> - trueDepth = @csIndent indentDepth, nodes[0], nodes[nodes.length - 1], depth + csIndentAndMark: (indentDepth, nodes, depth, classes = []) -> + trueDepth = @csIndent indentDepth, nodes[0], nodes[nodes.length - 1], depth, classes for node in nodes @mark node, depth + 1, 0, null, trueDepth @@ -952,10 +986,18 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( return helper.DISCOURAGE else if context.type in ['indent', 'segment'] - if 'block-only' in block.classes or + if ('block-only' in block.classes or 'mostly-block' in block.classes or 'any-drop' in block.classes or - block.type is 'segment' + 'blank-block' in block.classes or + block.type is 'segment') and not ('list' in context.classes) + return helper.ENCOURAGE + + else if ('mostly-value' in block.classes or + 'value-only' in block.classes or + 'any-drop' in block.classes or + 'blank-block' in block.classes) and + 'list' in context.classes return helper.ENCOURAGE else if 'mostly-value' in block.classes @@ -963,19 +1005,32 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'coffee-script'], ( return helper.DISCOURAGE - CoffeeScriptParser.parens = (leading, trailing, node, context) -> - trailing trailing().replace /\s*,\s*$/, '' + CoffeeScriptParser.parens = (prev, node, next, context) -> + if context? and 'list' in context.classes + node.trailing node.trailing().replace /\s*,\s*$/, '' + if next? + node.trailing node.trailing() + ',' + if prev? + prev.trailing prev.trailing().replace /\s*,\s*$/, '' + prev.trailing prev.trailing() + ',' + + if 'Obj' in node.classes + unless node.leading().match /.*{.*/ + node.leading '{' + node.leading() + unless node.trailing().match /.*}.*/ + node.trailing node.trailing() + '}' + return if context is null or context.type isnt 'socket' or context.precedence < node.precedence while true - if leading().match(/^\s*\(/)? and trailing().match(/\)\s*/)? - leading leading().replace(/^\s*\(\s*/, '') - trailing trailing().replace(/\s*\)\s*$/, '') + if node.leading().match(/^\s*\(/)? and node.trailing().match(/\)\s*/)? + node.leading node.leading().replace(/^\s*\(\s*/, '') + node.trailing node.trailing().replace(/\s*\)\s*$/, '') else break else - leading '(' + leading() - trailing trailing() + ')' + node.leading '(' + node.leading() + node.trailing node.trailing() + ')' return diff --git a/src/controller.coffee b/src/controller.coffee index ba9b5c80..72f18def 100644 --- a/src/controller.coffee +++ b/src/controller.coffee @@ -831,32 +831,49 @@ define ['droplet-helper', # Move the cursor to the end of it. return clone.end - Editor::spliceOut = (node) -> - leading = node.getLeadingText() - if node.start.next is node.end.prev - trailing = null + Editor::applyParens = (node, location) -> + if location? + container = location.container ? location.visParent() + if container? and container.type is 'block' + container = container.visParent() + + head = location + until not head? or head is container.start or head.type is 'blockEnd' + head = head.prev + if not head? or head is container.start + prev = null + else + prev = head.container + + head = location + until not head? or head is container.end or head.type is 'blockStart' + head = head.next + if not head? or head is container.end + next = null + else + next = head.container else - trailing = node.getTrailingText() + prev = next = container = null + + @mode.parens prev?.getParenModifier() ? null, node.getParenModifier(), next?.getParenModifier?() ? null, container?.getReader?() ? null - [leading, trailing] = @mode.parens leading, trailing, node.getReader(), null + return node - node.setLeadingText leading; node.setTrailingText trailing + Editor::spliceOut = (node) -> + @applyParens node, null node.spliceOut() - Editor::spliceIn = (node, location) -> - leading = node.getLeadingText() - if node.start.next is node.end.prev - trailing = null - else - trailing = node.getTrailingText() + Editor::spliceInRaw = (node, location) -> + @applyParens node, location - container = location.container ? location.visParent() + last = location.next - [leading, trailing] = @mode.parens leading, trailing, node.getReader(), - (if container.type is 'block' then container.visParent() else container)?.getReader?() ? null + location.append node.start + node.end.append last - node.setLeadingText leading; node.setTrailingText trailing + Editor::spliceIn = (node, location) -> + @applyParens node, location node.spliceIn location @@ -1066,16 +1083,15 @@ define ['droplet-helper', if head instanceof model.StartToken acceptLevel = @getAcceptLevel @draggingBlock, head.container unless acceptLevel is helper.FORBID - dropPoint = @view.getViewNodeFor(head.container).dropPoint - - if dropPoint? + for dropPoint, i in @view.getViewNodeFor(head.container).dropPoints @dropPointQuadTree.insert x: dropPoint.x y: dropPoint.y w: 0 h: 0 acceptLevel: acceptLevel - _ice_node: head.container + _droplet_node: head.container + _droplet_index: i head = head.next @@ -1123,7 +1139,7 @@ define ['droplet-helper', mainPoint = @trackerPointToMain(position) - best = null; min = Infinity + best = bestIndex = null; min = Infinity # Check to see if the tree is empty; # if it is, drop on the tree always @@ -1149,14 +1165,15 @@ define ['droplet-helper', distance = mainPoint.from(point) distance.y *= 2; distance = distance.magnitude() if distance < min and mainPoint.from(point).magnitude() < MAX_DROP_DISTANCE and - @view.getViewNodeFor(point._ice_node).highlightArea? - best = point._ice_node + @view.getViewNodeFor(point._droplet_node).highlightAreas[point._droplet_index]? + best = point._droplet_node + bestIndex = point._droplet_index min = distance if best isnt @lastHighlight @clearHighlightCanvas() - if best? then @view.getViewNodeFor(best).highlightArea.draw @highlightCtx + if best? then @view.getViewNodeFor(best).highlightAreas[bestIndex].draw @highlightCtx @lastHighlight = best @@ -1199,10 +1216,17 @@ define ['droplet-helper', switch @lastHighlight.type when 'indent', 'socket' @addMicroUndoOperation new DropOperation @draggingBlock, @lastHighlight.start - @spliceIn @draggingBlock, @lastHighlight.start #MUTATION + if @lastHighlight.type is 'indent' and 'list' in @lastHighlight.classes and + @lastHighlight.lineLength() is 0 + @spliceInRaw @draggingBlock, @lastHighlight.start #MUTATION + else + @spliceIn @draggingBlock, @lastHighlight.start #MUTATION when 'block' @addMicroUndoOperation new DropOperation @draggingBlock, @lastHighlight.end - @spliceIn @draggingBlock, @lastHighlight.end #MUTATION + if @lastHighlight.inList() and @lastHighlight.parent.lineLength() is 0 + @spliceInRaw @draggingBlock, @lastHighlight.end + else + @spliceIn @draggingBlock, @lastHighlight.end #MUTATION else if @lastHighlight is @tree @addMicroUndoOperation new DropOperation @draggingBlock, @tree.start @@ -1215,10 +1239,10 @@ define ['droplet-helper', # Reparse the parent if we are # in a socket # - # TODO "reparseable" property, bubble up # TODO performance on large programs if @lastHighlight.type is 'socket' - @reparseRawReplace @draggingBlock.parent.parent + @reparseRawReplace @draggingBlock.parent.parent.parentWithQuality (x) => + x.type is 'block' and @mode.canReparse x else # If what we've dropped has a socket in it, @@ -1244,6 +1268,7 @@ define ['droplet-helper', if newParse.start.next.container.end is newParse.end.prev and newBlock?.type is 'block' @addMicroUndoOperation new ReparseOperation oldBlock, newBlock + @applyParens newBlock, oldBlock.parent if @cursor.hasParent oldBlock pos = @getRecoverableCursorPosition() @@ -1844,7 +1869,9 @@ define ['droplet-helper', shouldRecoverCursor = false cursorPosition = cursorParent = null - if @cursor.hasParent @textFocus.parent + reparseParent = @reparseParent @textFocus + + if @cursor.hasParent reparseParent shouldRecoverCursor = true cursorPosition = @getRecoverableCursorPosition() @@ -1875,7 +1902,7 @@ define ['droplet-helper', shouldPop = true # TODO make 'reparsable' property, bubble up until then - unless @reparseRawReplace @textFocus.parent + unless @reparseRawReplace(reparseParent) # If we can't reparse the parent, abort all reparses @populateSocket @textFocus, originalText @@ -2555,8 +2582,10 @@ define ['droplet-helper', if head.type is 'socketStart' and (head.next.type is 'text' or head.next is head.container.end) # Avoid problems with reparses - if @textFocus? and head.container.hasParent @textFocus.parent - persistentParent = @textFocus.getCommonParent(head.container).parent + if @textFocus? + reparseParent = @reparseParent @textFocus + if @textFocus? and head.container.hasParent reparseParent + persistentParent = reparseParent.getCommonParent(head.container).parent chars = getCharactersTo persistentParent, head.container.start @setTextInputFocus null @@ -2653,7 +2682,14 @@ define ['droplet-helper', return head.container + Editor::reparseParent = (node) -> + parent = node.parent.parentWithQuality((x) => + x.type is 'block' and @mode.canReparse x) + return parent + hook 'keydown', 0, (event, state) -> + if @textFocus? + reparseParent = @reparseParent @textFocus if event.which isnt TAB_KEY then return if event.shiftKey if @textFocus? then head = @textFocus.start @@ -2664,8 +2700,8 @@ define ['droplet-helper', head = head.prev if head? - if @textFocus? and head.container.hasParent @textFocus.parent - persistentParent = @textFocus.getCommonParent(head.container).parent + if @textFocus? and head.container.hasParent reparseParent + persistentParent = reparseParent.getCommonParent(head.container).parent chars = getCharactersTo persistentParent, head.container.start @setTextInputFocus null @@ -2685,8 +2721,8 @@ define ['droplet-helper', (head.container.start.next.type is 'text' or head.container.start.next is head.container.end) head = head.next if head? - if @textFocus? and head.container.hasParent @textFocus.parent - persistentParent = @textFocus.getCommonParent(head.container).parent + if @textFocus? and head.container.hasParent reparseParent + persistentParent = reparseParent.getCommonParent(head.container).parent chars = getCharactersTo persistentParent, head.container.start @setTextInputFocus null diff --git a/src/javascript.coffee b/src/javascript.coffee index c6e8e131..0e616170 100644 --- a/src/javascript.coffee +++ b/src/javascript.coffee @@ -462,26 +462,26 @@ define ['droplet-helper', 'droplet-model', 'droplet-parser', 'acorn'], (helper, @mark indentDepth, node, depth + 1, bounds - JavaScriptParser.parens = (leading, trailing, node, context) -> + JavaScriptParser.parens = (prev, node, next, context) -> if context?.type is 'socket' or (not context? and 'mostly-value' in node.classes or 'value-only' in node.classes) or 'ends-with-brace' in node.classes or node.type is 'segment' - trailing trailing().replace(/;?\s*$/, '') + node.trailing node.trailing().replace(/;?\s*$/, '') else - trailing trailing().replace(/;?\s*$/, ';') + node.trailing node.trailing().replace(/;?\s*$/, ';') if context is null or context.type isnt 'socket' or context.precedence > node.precedence while true - if leading().match(/^\s*\(/)? and trailing().match(/\)\s*/)? - leading leading().replace(/^\s*\(\s*/, '') - trailing trailing().replace(/\s*\)\s*$/, '') + if node.leading().match(/^\s*\(/)? and node.trailing().match(/\)\s*/)? + node.leading node.leading().replace(/^\s*\(\s*/, '') + node.trailing node.trailing().replace(/\s*\)\s*$/, '') else break else - leading '(' + leading() - trailing trailing() + ')' + node.leading '(' + node.leading() + node.trailing node.trailing() + ')' JavaScriptParser.drop = (block, context, pred) -> if context.type is 'socket' diff --git a/src/model.coffee b/src/model.coffee index adcf8b73..6f383f1c 100644 --- a/src/model.coffee +++ b/src/model.coffee @@ -14,6 +14,35 @@ define ['droplet-helper'], (helper) -> _id = 0 + # FOR TESTING ONLY + applyParens = (mode, node, location) -> + if location? + container = location.container ? location.visParent() + if container? and container.type is 'block' + container = container.visParent() + + head = location + until head is container.start or head.type is 'blockEnd' + head = head.prev + if head is container.start + prev = null + else + prev = head.container + + head = location + until head is container.end or head.type is 'blockStart' + head = head.next + if head is container.end + next = null + else + next = head.container + else + prev = next = container = null + + mode.parens prev?.getParenModifier() ? null, node.getParenModifier(), next?.getParenModifier?() ? null, container?.getReader?() ? null + + return node + # Getter/setter utility function Function::trigger = (prop, get, set) -> Object.defineProperty @prototype, prop, @@ -107,6 +136,26 @@ define ['droplet-helper'], (helper) -> classes: @classes } + getParenModifier: -> + leadingFn = (value) => + if value? + @setLeadingText value + return @getLeadingText() + + trailingFn = (value) => + if value? + @setTrailingText value + return @getTrailingText() + + return { + leading: leadingFn + trailing: trailingFn + id: @id + type: @type + precedence: @precedence + classes: @classes + } + hasParent: (parent) -> head = @ until head in [parent, null] @@ -114,6 +163,15 @@ define ['droplet-helper'], (helper) -> return head is parent + inList: -> + @parent? and 'list' in @visParent().classes + + inMultiLineList: -> + @inList() and @visParent().lineLength() > 0 + + inSingleLineList: -> + @inList() and @visParent().lineLength() is 0 + getCommonParent: (other) -> head = @ until other.hasParent head @@ -183,6 +241,7 @@ define ['droplet-helper'], (helper) -> setLeadingText: (value) -> if value? + @version++ if @start.next.type is 'text' if value.length is 0 @start.next.remove() @@ -193,6 +252,7 @@ define ['droplet-helper'], (helper) -> setTrailingText: (value) -> if value? + @version++ if @end.prev.type is 'text' if value.length is 0 @end.prev.remove() @@ -201,6 +261,18 @@ define ['droplet-helper'], (helper) -> else unless value.length is 0 @end.insertBefore new TextToken value + each: (f) -> + head = @start.next + until head is @end + f head + head = head.next + return true + + lineLength: -> + length = 0 + @each (el) -> length++ if el.type is 'newline' + return length + # ## clone ## # Clone this container, with all the token inside, # but with no linked-list pointers in common. @@ -371,22 +443,12 @@ define ['droplet-helper'], (helper) -> # USED FOR TESTING ONLY moveTo: (token, mode) -> if @start.prev? or @end.next? - leading = @getLeadingText() - trailing = @getTrailingText() - - [leading, trailing] = mode.parens leading, trailing, @, null - - @setLeadingText leading; @setTrailingText trailing + applyParens mode, @, null @spliceOut() if token? - leading = @getLeadingText() - trailing = @getTrailingText() - - [leading, trailing] = mode.parens leading, trailing, @, (token.container ? token.parent) - - @setLeadingText leading; @setTrailingText trailing + applyParens mode, @, token @spliceIn token @@ -497,11 +559,17 @@ define ['droplet-helper'], (helper) -> return @end.nextVisibleToken() in [@parent?.end, @parent?.parent?.end, null] or @end.nextVisibleToken()?.type in ['newline', 'indentStart', 'indentEnd'] + parentWithQuality: (fn) -> + parent = @ + until fn parent + parent = parent.parent + return parent + visParent: -> - head = @parent - while head?.type is 'segment' and head.isLassoSegment - head = head.parent - return head + if @parent? + @parent.parentWithQuality (x) -> not (x?.type is 'segment' and x.isLassoSegment) + else + return null # Line mark mutators addLineMark: (mark) -> diff --git a/src/parser.coffee b/src/parser.coffee index aae5643d..00ca43ab 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -104,7 +104,6 @@ define ['droplet-helper', 'droplet-model'], (helper, model) -> opts.color, opts.socketLevel, opts.classes, - false @addMarkup block, opts.bounds, opts.depth @@ -205,7 +204,7 @@ define ['droplet-helper', 'droplet-model'], (helper, model) -> # Construct a handwritten block with the given # text inside constructHandwrittenBlock: (text) -> - block = new model.Block 0, 'blank', helper.ANY_DROP, false + block = new model.Block 0, 'blank', helper.ANY_DROP, ['Value', 'blank-block'] socket = new model.Socket 0, true textToken = new model.TextToken text @@ -308,7 +307,7 @@ define ['droplet-helper', 'droplet-model'], (helper, model) -> # If the a block is embedded # directly in another block, throw. if stack[stack.length - 1]?.type is 'block' - throw new Error 'Improper parser: block cannot nest immediately inside another block.' + throw new Error "Improper parser: block cannot nest immediately inside another block: line #{i}." when 'socketStart' # A socket is only allowed to be directly inside a block. @@ -323,7 +322,7 @@ define ['droplet-helper', 'droplet-model'], (helper, model) -> stack.push mark.token.container else if mark.token instanceof model.EndToken unless mark.token.container is stack[stack.length - 1] - throw new Error "Improper parser: #{head.container.type} ended too early." + throw new Error "Improper parser: #{head.container.type} ended too early: line #{i}" stack.pop() # Append the token @@ -373,7 +372,7 @@ define ['droplet-helper', 'droplet-model'], (helper, model) -> container = new model.Socket attributes.precedence, attributes.handritten, attributes.classes?.split?(' ') when 'indent' - container = new model.Indent attributes.prefix, attributes.classe?.split?(' ') + container = new model.Indent attributes.prefix, attributes.classes?.split?(' ') when 'segment' # Root segment is optional unless stack.length is 0 @@ -438,18 +437,18 @@ define ['droplet-helper', 'droplet-model'], (helper, model) -> else head = head.next - Parser.parens = (leading, trailing, node, context) -> + Parser.parens = (prev, node, next, context) -> if context is null or context.type isnt 'socket' or context?.precedence < node.precedence while true - if leading().match(/^\s*\(/)? and trailing().match(/\)\s*/)? - leading leading().replace(/^\s*\(\s*/, '') - trailing trailing().replace(/^\s*\)\s*/, '') + if node.leading().match(/^\s*\(/)? and node.trailing().match(/\)\s*/)? + node.leading node.leading().replace(/^\s*\(\s*/, '') + node.trailing node.trailing().replace(/^\s*\)\s*/, '') else break else - leading '(' + leading() - trailing trailing() + ')' + node.leading '(' + node.leading() + node.trailing node.trailing() + ')' Parser.drop = (block, context, pred) -> if block.type is 'segment' and context.type is 'socket' @@ -470,28 +469,16 @@ define ['droplet-helper', 'droplet-model'], (helper, model) -> opts ?= wrapAtRoot: true return @createParser(text)._parse opts - parens: (leading, trailing, node, context) -> - # leadingFn is always a getter/setter for leading - leadingFn = (value) -> - if value? - leading = value - return leading - - # trailingFn may either get/set leading or trailing; - # will point to leading if leading is the only token, - # but will point to trailing otherwise. - if trailing? - trailingFn = (value) -> - if value? - trailing = value - return trailing - else - trailingFn = leadingFn - - CustomParser.parens leadingFn, trailingFn, node, context - - return [leading, trailing] + parens: (prev, node, next, context) -> + CustomParser.parens prev, node, next, context + return null drop: (block, context, pred) -> CustomParser.drop block, context, pred + canReparse: (node) -> + if node.parent? and 'list' in node.parent.classes + return false + else + return true + return exports diff --git a/src/view.coffee b/src/view.coffee index 7c335626..d795d0b6 100644 --- a/src/view.coffee +++ b/src/view.coffee @@ -21,6 +21,8 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model CARRIAGE_ARROW_NONE = 2 CARRIAGE_GROW_DOWN = 3 + LEFT_BULLET_WIDTH = 13 + BULLET_ARROW_WIDTH = 6 DROPDOWN_ARROW_HEIGHT = 8 DROP_TRIANGLE_COLOR = '#555' @@ -44,6 +46,7 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model shadowBlur: 5 ctx: document.createElement('canvas').getContext('2d') colors: + blank: '#dfdfdf' error: '#ff0000' return: '#fff59d' # yellow control: '#ffcc80' # orange @@ -203,7 +206,8 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model # *Seventh pass variables* # computeDropAreas # each one is a @view.draw.Path (or null) - @dropArea = @highlightArea = null + @dropPoints = [] + @highlightAreas = [] # Versions. The corresponding # Model will keep corresponding version @@ -650,7 +654,7 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model # # If we cannot drop something on this node # (e.g. a socket that already contains a block), - # set `@dropArea` to null. + # set `@dropPoints` to [] # # Simultaneously, compute `@highlightArea`, which # is the white polygon that lights up @@ -1294,6 +1298,10 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model if @multilineChildrenData[line] is NO_MULTILINE # Draw the left edge of the bounding box. left.push new @view.draw.Point bounds.x, bounds.y + if @model.inMultiLineList() + left.push new @view.draw.Point bounds.x, (bounds.y + bounds.bottom()) / 2 - BULLET_ARROW_WIDTH + left.push new @view.draw.Point bounds.x + BULLET_ARROW_WIDTH, (bounds.y + bounds.bottom()) / 2 + left.push new @view.draw.Point bounds.x, (bounds.y + bounds.bottom()) / 2 + BULLET_ARROW_WIDTH left.push new @view.draw.Point bounds.x, bounds.bottom() # Draw the right edge of the bounding box. @@ -1304,6 +1312,10 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model if @multilineChildrenData[line] is MULTILINE_START # Draw the left edge of the bounding box. left.push new @view.draw.Point bounds.x, bounds.y + if @model.inMultiLineList() + left.push new @view.draw.Point bounds.x, (bounds.y + bounds.bottom()) / 2 - BULLET_ARROW_WIDTH + left.push new @view.draw.Point bounds.x + BULLET_ARROW_WIDTH, (bounds.y + bounds.bottom()) / 2 + left.push new @view.draw.Point bounds.x, (bounds.y + bounds.bottom()) / 2 + BULLET_ARROW_WIDTH left.push new @view.draw.Point bounds.x, bounds.bottom() # Find the multiline child that's starting on this line, @@ -1322,7 +1334,7 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model else right.push new @view.draw.Point bounds.right(), bounds.y right.push new @view.draw.Point bounds.right(), multilineBounds.y - if multilineChild.child.type is 'indent' + if multilineChild.child.type is 'indent' and not ('list' in multilineChild.child.classes) @addTab right, new @view.draw.Point multilineBounds.x + @view.opts.tabOffset, multilineBounds.y right.push new @view.draw.Point multilineBounds.x, multilineBounds.y right.push new @view.draw.Point multilineBounds.x, multilineBounds.bottom() @@ -1341,6 +1353,10 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model unless @multilineChildrenData[line - 1] in [MULTILINE_START, MULTILINE_END_START] and multilineChild.child.type is 'indent' right.push new @view.draw.Point multilineBounds.x, bounds.y + if 'list' in multilineChild.child.classes and @view.getViewNodeFor(multilineChild.child).lineChildren[line - multilineChild.startLine][0].startLine is line - multilineChild.startLine + right.push new @view.draw.Point multilineBounds.x, (multilineBounds.y + multilineBounds.bottom()) / 2 - BULLET_ARROW_WIDTH + right.push new @view.draw.Point multilineBounds.x + BULLET_ARROW_WIDTH, (multilineBounds.y + multilineBounds.bottom()) / 2 + right.push new @view.draw.Point multilineBounds.x, (multilineBounds.y + multilineBounds.bottom()) / 2 + BULLET_ARROW_WIDTH right.push new @view.draw.Point multilineBounds.x, bounds.bottom() # Case 4. End of an indent. @@ -1356,9 +1372,13 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model unless @multilineChildrenData[line - 1] in [MULTILINE_START, MULTILINE_END_START] and multilineChild.child.type is 'indent' right.push new @view.draw.Point multilineBounds.x, multilineBounds.y + if 'list' in multilineChild.child.classes and @view.getViewNodeFor(multilineChild.child).lineChildren[line - multilineChild.startLine][0].startLine is line - multilineChild.startLine + right.push new @view.draw.Point multilineBounds.x, (multilineBounds.y + multilineBounds.bottom()) / 2 - BULLET_ARROW_WIDTH + right.push new @view.draw.Point multilineBounds.x + BULLET_ARROW_WIDTH, (multilineBounds.y + multilineBounds.bottom()) / 2 + right.push new @view.draw.Point multilineBounds.x, (multilineBounds.y + multilineBounds.bottom()) / 2 + BULLET_ARROW_WIDTH right.push new @view.draw.Point multilineBounds.x, multilineBounds.bottom() - if multilineChild.child.type is 'indent' + if multilineChild.child.type is 'indent' and not ('list' in multilineChild.child.classes) @addTabReverse right, new @view.draw.Point multilineBounds.x + @view.opts.tabOffset, multilineBounds.bottom() right.push new @view.draw.Point multilineBounds.right(), multilineBounds.bottom() @@ -1503,8 +1523,9 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model # Special case for indents that start with newlines; # don't do any of the same-line-start multiline stuff. if multilineChild.child.type is 'indent' and multilineChild.child.start.next.type is 'newline' - right.push new @view.draw.Point @bounds[line].right(), glueTop + right.push new @view.draw.Point @bounds[line].right(), glueTop + unless ('list' in multilineChild.child.classes) @addTab right, new @view.draw.Point(@bounds[line + 1].x + @view.opts.indentWidth + @view.opts.tabOffset, glueTop), true @@ -1586,7 +1607,9 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model # ## computeOwnDropArea # By default, we will not have a # drop area (not be droppable). - computeOwnDropArea: -> @dropArea = @highlightArea = null + computeOwnDropArea: -> + @dropPoints = [] + @highlightAreas = [] # ## shouldAddTab # By default, we will ask @@ -1646,10 +1669,21 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model size.width = Math.max size.width, @view.opts.tabWidth + @view.opts.tabOffset + if @model.inMultiLineList() + @minDimensions[0].width += LEFT_BULLET_WIDTH + return null + computeBoundingBoxX: (left, line) -> + if line is 0 and @model.inMultiLineList() + super left, line, LEFT_BULLET_WIDTH + else + super left, line + shouldAddTab: -> - if @model.parent? + if @model.inList() + return false + else if @model.parent? parent = @model.visParent() parent?.type isnt 'socket' else not ('mostly-value' in @model.classes or @@ -1658,65 +1692,84 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model computeOwnPath: -> super - @path.style.fillColor = @view.getColor @model.color - @path.style.strokeColor = '#888' - - @path.bevel = true + if 'blank-block' in @model.classes and @model.inSingleLineList() + @path.style.fillColor = 'transparent' + @path.style.strokeColor = 'transparent' + @path.bevel = false + else + @path.style.fillColor = @view.getColor @model.color + @path.style.strokeColor = '#888' + @path.bevel = true return @path computeOwnDropArea: -> - # Our drop area is a rectangle of - # height dropAreaHeight and a width - # equal to our last line width, - # positioned at the bottom of our last line. - if @carriageArrow is CARRIAGE_ARROW_INDENT - parentViewNode = @view.getViewNodeFor @model.visParent() - destinationBounds = parentViewNode.bounds[1] - - @dropPoint = new @view.draw.Point destinationBounds.x, destinationBounds.y - lastBoundsLeft = destinationBounds.x - lastBoundsRight = destinationBounds.right() - else if @carriageArrow is CARRIAGE_ARROW_SIDEALONG - parentViewNode = @view.getViewNodeFor @model.visParent() - destinationBounds = parentViewNode.bounds[1] - - @dropPoint = new @view.draw.Point destinationBounds.x, - @bounds[@lineLength - 1].bottom() + @view.opts.padding - lastBoundsLeft = destinationBounds.x - lastBoundsRight = @bounds[@lineLength - 1].right() + if @model.inSingleLineList() + lastBounds = @bounds[@bounds.length - 1] + @dropPoints[0] = new @view.draw.Point lastBounds.right(), lastBounds.y + lastBounds.height / 2 + + @highlightAreas[0] = highlightArea = new @view.draw.Path() + + highlightArea.push new @view.draw.Point lastBounds.right() - 5, lastBounds.y + highlightArea.push new @view.draw.Point lastBounds.right() + 5, lastBounds.y + highlightArea.push new @view.draw.Point lastBounds.right() + 5, lastBounds.bottom() + highlightArea.push new @view.draw.Point lastBounds.right() - 5, lastBounds.bottom() + + highlightArea.style.lineWidth = 1 + highlightArea.style.strokeColor = '#ff0' + highlightArea.style.fillColor = '#ff0' else - @dropPoint = new @view.draw.Point @bounds[@lineLength - 1].x, @bounds[@lineLength - 1].bottom() - lastBoundsLeft = @bounds[@lineLength - 1].x - lastBoundsRight = @bounds[@lineLength - 1].right() + # Our drop area is a rectangle of + # height dropAreaHeight and a width + # equal to our last line width, + # positioned at the bottom of our last line. + if @carriageArrow is CARRIAGE_ARROW_INDENT + parentViewNode = @view.getViewNodeFor @model.visParent() + destinationBounds = parentViewNode.bounds[1] + + @dropPoints[0] = new @view.draw.Point destinationBounds.x, destinationBounds.y + lastBoundsLeft = destinationBounds.x + lastBoundsRight = destinationBounds.right() + else if @carriageArrow is CARRIAGE_ARROW_SIDEALONG + parentViewNode = @view.getViewNodeFor @model.visParent() + destinationBounds = parentViewNode.bounds[1] + + @dropPoints[0] = new @view.draw.Point destinationBounds.x, + @bounds[@lineLength - 1].bottom() + @view.opts.padding + lastBoundsLeft = destinationBounds.x + lastBoundsRight = @bounds[@lineLength - 1].right() + else + @dropPoints[0] = new @view.draw.Point @bounds[@lineLength - 1].x, @bounds[@lineLength - 1].bottom() + lastBoundsLeft = @bounds[@lineLength - 1].x + lastBoundsRight = @bounds[@lineLength - 1].right() - # Our highlight area is the a rectangle in the same place, - # with a height that can be given by a different option. + # Our highlight area is the a rectangle in the same place, + # with a height that can be given by a different option. - @highlightArea = new @view.draw.Path() - highlightAreaPoints = [] + highlightArea = new @view.draw.Path() - highlightAreaPoints.push new @view.draw.Point lastBoundsLeft, @dropPoint.y - @view.opts.highlightAreaHeight / 2 + @view.opts.bevelClip - highlightAreaPoints.push new @view.draw.Point lastBoundsLeft + @view.opts.bevelClip, @dropPoint.y - @view.opts.highlightAreaHeight / 2 + highlightArea.push new @view.draw.Point lastBoundsLeft, @dropPoints[0].y - @view.opts.highlightAreaHeight / 2 + @view.opts.bevelClip + highlightArea.push new @view.draw.Point lastBoundsLeft + @view.opts.bevelClip, @dropPoints[0].y - @view.opts.highlightAreaHeight / 2 - @addTabReverse highlightAreaPoints, new @view.draw.Point lastBoundsLeft + @view.opts.tabOffset, @dropPoint.y - @view.opts.highlightAreaHeight / 2 + @addTabReverse highlightArea, new @view.draw.Point lastBoundsLeft + @view.opts.tabOffset, @dropPoints[0].y - @view.opts.highlightAreaHeight / 2 - highlightAreaPoints.push new @view.draw.Point lastBoundsRight - @view.opts.bevelClip, @dropPoint.y - @view.opts.highlightAreaHeight / 2 - highlightAreaPoints.push new @view.draw.Point lastBoundsRight, @dropPoint.y - @view.opts.highlightAreaHeight / 2 + @view.opts.bevelClip + highlightArea.push new @view.draw.Point lastBoundsRight - @view.opts.bevelClip, @dropPoints[0].y - @view.opts.highlightAreaHeight / 2 + highlightArea.push new @view.draw.Point lastBoundsRight, @dropPoints[0].y - @view.opts.highlightAreaHeight / 2 + @view.opts.bevelClip - highlightAreaPoints.push new @view.draw.Point lastBoundsRight, @dropPoint.y + @view.opts.highlightAreaHeight / 2 - @view.opts.bevelClip - highlightAreaPoints.push new @view.draw.Point lastBoundsRight - @view.opts.bevelClip, @dropPoint.y + @view.opts.highlightAreaHeight / 2 + highlightArea.push new @view.draw.Point lastBoundsRight, @dropPoints[0].y + @view.opts.highlightAreaHeight / 2 - @view.opts.bevelClip + highlightArea.push new @view.draw.Point lastBoundsRight - @view.opts.bevelClip, @dropPoints[0].y + @view.opts.highlightAreaHeight / 2 - @addTab highlightAreaPoints, new @view.draw.Point lastBoundsLeft + @view.opts.tabOffset, @dropPoint.y + @view.opts.highlightAreaHeight / 2 + @addTab highlightArea, new @view.draw.Point lastBoundsLeft + @view.opts.tabOffset, @dropPoints[0].y + @view.opts.highlightAreaHeight / 2 - highlightAreaPoints.push new @view.draw.Point lastBoundsLeft + @view.opts.bevelClip, @dropPoint.y + @view.opts.highlightAreaHeight / 2 - highlightAreaPoints.push new @view.draw.Point lastBoundsLeft, @dropPoint.y + @view.opts.highlightAreaHeight / 2 - @view.opts.bevelClip + highlightArea.push new @view.draw.Point lastBoundsLeft + @view.opts.bevelClip, @dropPoints[0].y + @view.opts.highlightAreaHeight / 2 + highlightArea.push new @view.draw.Point lastBoundsLeft, @dropPoints[0].y + @view.opts.highlightAreaHeight / 2 - @view.opts.bevelClip - @highlightArea.push point for point in highlightAreaPoints + highlightArea.style.lineWidth = 1 + highlightArea.style.strokeColor = '#ff0' + highlightArea.style.fillColor = '#ff0' + + @highlightAreas[0] = highlightArea - @highlightArea.style.lineWidth = 1 - @highlightArea.style.strokeColor = '#ff0' - @highlightArea.style.fillColor = '#ff0' # # SocketViewNode class SocketViewNode extends ContainerViewNode @@ -1827,13 +1880,16 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model # things. computeOwnDropArea: -> if @model.start.next.type is 'blockStart' - @dropArea = @highlightArea = null + @dropPoints = [] + @highlightAreas = [] else - @dropPoint = @bounds[0].upperLeftCorner() - @highlightArea = @path.clone() - @highlightArea.noclip = true - @highlightArea.style.strokeColor = '#FF0' - @highlightArea.style.lineWidth = @view.opts.padding + @dropPoints[0] = @bounds[0].upperLeftCorner() + highlightArea = @path.clone() + highlightArea.noclip = true + highlightArea.style.strokeColor = '#FF0' + highlightArea.style.lineWidth = @view.opts.padding + + @highlightAreas[0] = highlightArea # # IndentViewNode class IndentViewNode extends ContainerViewNode @@ -1898,42 +1954,59 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model # equal to our first line width, # positioned at the top of our firs tline computeOwnDropArea: -> - lastBounds = new @view.draw.NoRectangle() - if @model.start.next.type is 'newline' - @dropPoint = @bounds[1].upperLeftCorner() - lastBounds.copy @bounds[1] + if @model.classes and @lineLength is 1 + firstBounds = @bounds[0] + @dropPoints[0] = new @view.draw.Point firstBounds.x, firstBounds.y + firstBounds.height / 2 + + @highlightAreas[0] = highlightArea = new @view.draw.Path() + + highlightArea.push new @view.draw.Point firstBounds.x - 5, firstBounds.y + highlightArea.push new @view.draw.Point firstBounds.x + 5, firstBounds.y + highlightArea.push new @view.draw.Point firstBounds.x + 5, firstBounds.bottom() + highlightArea.push new @view.draw.Point firstBounds.x - 5, firstBounds.bottom() + + highlightArea.style.lineWidth = 1 + highlightArea.style.strokeColor = '#ff0' + highlightArea.style.fillColor = '#ff0' else - @dropPoint = @bounds[0].upperLeftCorner() - lastBounds.copy @bounds[0] - lastBounds.width = Math.max lastBounds.width, @view.opts.indentDropAreaMinWidth + lastBounds = new @view.draw.NoRectangle() + if @model.start.next.type is 'newline' + @dropPoints[0] = @bounds[1].upperLeftCorner() + lastBounds.copy @bounds[1] + else + @dropPoints[0] = @bounds[0].upperLeftCorner() + lastBounds.copy @bounds[0] + lastBounds.width = Math.max lastBounds.width, @view.opts.indentDropAreaMinWidth - # Our highlight area is the a rectangle in the same place, - # with a height that can be given by a different option. + # Our highlight area is the a rectangle in the same place, + # with a height that can be given by a different option. - @highlightArea = new @view.draw.Path() - highlightAreaPoints = [] + highlightArea = new @view.draw.Path() + highlightAreaPoints = [] + + highlightAreaPoints.push new @view.draw.Point lastBounds.x, lastBounds.y - @view.opts.highlightAreaHeight / 2 + @view.opts.bevelClip + highlightAreaPoints.push new @view.draw.Point lastBounds.x + @view.opts.bevelClip, lastBounds.y - @view.opts.highlightAreaHeight / 2 - highlightAreaPoints.push new @view.draw.Point lastBounds.x, lastBounds.y - @view.opts.highlightAreaHeight / 2 + @view.opts.bevelClip - highlightAreaPoints.push new @view.draw.Point lastBounds.x + @view.opts.bevelClip, lastBounds.y - @view.opts.highlightAreaHeight / 2 + @addTabReverse highlightAreaPoints, new @view.draw.Point lastBounds.x + @view.opts.tabOffset, lastBounds.y - @view.opts.highlightAreaHeight / 2 - @addTabReverse highlightAreaPoints, new @view.draw.Point lastBounds.x + @view.opts.tabOffset, lastBounds.y - @view.opts.highlightAreaHeight / 2 + highlightAreaPoints.push new @view.draw.Point lastBounds.right() - @view.opts.bevelClip, lastBounds.y - @view.opts.highlightAreaHeight / 2 + highlightAreaPoints.push new @view.draw.Point lastBounds.right(), lastBounds.y - @view.opts.highlightAreaHeight / 2 + @view.opts.bevelClip - highlightAreaPoints.push new @view.draw.Point lastBounds.right() - @view.opts.bevelClip, lastBounds.y - @view.opts.highlightAreaHeight / 2 - highlightAreaPoints.push new @view.draw.Point lastBounds.right(), lastBounds.y - @view.opts.highlightAreaHeight / 2 + @view.opts.bevelClip + highlightAreaPoints.push new @view.draw.Point lastBounds.right(), lastBounds.y + @view.opts.highlightAreaHeight / 2 - @view.opts.bevelClip + highlightAreaPoints.push new @view.draw.Point lastBounds.right() - @view.opts.bevelClip, lastBounds.y + @view.opts.highlightAreaHeight / 2 - highlightAreaPoints.push new @view.draw.Point lastBounds.right(), lastBounds.y + @view.opts.highlightAreaHeight / 2 - @view.opts.bevelClip - highlightAreaPoints.push new @view.draw.Point lastBounds.right() - @view.opts.bevelClip, lastBounds.y + @view.opts.highlightAreaHeight / 2 + @addTab highlightAreaPoints, new @view.draw.Point lastBounds.x + @view.opts.tabOffset, lastBounds.y + @view.opts.highlightAreaHeight / 2 - @addTab highlightAreaPoints, new @view.draw.Point lastBounds.x + @view.opts.tabOffset, lastBounds.y + @view.opts.highlightAreaHeight / 2 + highlightAreaPoints.push new @view.draw.Point lastBounds.x + @view.opts.bevelClip, lastBounds.y + @view.opts.highlightAreaHeight / 2 + highlightAreaPoints.push new @view.draw.Point lastBounds.x, lastBounds.y + @view.opts.highlightAreaHeight / 2 - @view.opts.bevelClip - highlightAreaPoints.push new @view.draw.Point lastBounds.x + @view.opts.bevelClip, lastBounds.y + @view.opts.highlightAreaHeight / 2 - highlightAreaPoints.push new @view.draw.Point lastBounds.x, lastBounds.y + @view.opts.highlightAreaHeight / 2 - @view.opts.bevelClip + highlightArea.push point for point in highlightAreaPoints - @highlightArea.push point for point in highlightAreaPoints + highlightArea.style.lineWidth = 1 + highlightArea.style.strokeColor = '#ff0' + highlightArea.style.fillColor = '#ff0' - @highlightArea.style.lineWidth = 1 - @highlightArea.style.strokeColor = '#ff0' - @highlightArea.style.fillColor = '#ff0' + @highlightAreas[0] = highlightArea # # SegmentViewNode # Represents a Segment. Draws little, but @@ -1952,11 +2025,11 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model # can be dropped at their beginning. computeOwnDropArea: -> if @model.isLassoSegment - return @dropArea = null + return @dropPoints = [] else - @dropPoint = @bounds[0].upperLeftCorner() + @dropPoints[0] = @bounds[0].upperLeftCorner() - @highlightArea = new @view.draw.Path() + highlightArea = new @view.draw.Path() highlightAreaPoints = [] lastBounds = new @view.draw.NoRectangle() @@ -1979,10 +2052,12 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model highlightAreaPoints.push new @view.draw.Point lastBounds.x + @view.opts.bevelClip, lastBounds.y + @view.opts.highlightAreaHeight / 2 highlightAreaPoints.push new @view.draw.Point lastBounds.x, lastBounds.y + @view.opts.highlightAreaHeight / 2 - @view.opts.bevelClip - @highlightArea.push point for point in highlightAreaPoints + highlightArea.push point for point in highlightAreaPoints + + highlightArea.style.fillColor = '#ff0' + highlightArea.style.strokeColor = '#ff0' - @highlightArea.style.fillColor = '#ff0' - @highlightArea.style.strokeColor = '#ff0' + @highlightAreas[0] = highlightArea return null @@ -2116,6 +2191,8 @@ define ['droplet-helper', 'droplet-draw', 'droplet-model'], (helper, draw, model return '#' + (twoDigitHex(k) for k in rgb).join '' avgColor = (a, factor, b) -> + if a is 'transparent' or b is 'transparent' + return 'transparent' a = toRGB a b = toRGB b diff --git a/test/coffee/tests.coffee b/test/coffee/tests.coffee index 4820f384..828ccdf8 100644 --- a/test/coffee/tests.coffee +++ b/test/coffee/tests.coffee @@ -794,8 +794,8 @@ require ['droplet-helper', 'droplet-model', 'droplet-parser', 'droplet-coffee', strictEqual blockView.carriageArrow, 1, 'Carriage arrow flag is set' - strictEqual blockView.dropPoint.x, view_.opts.indentWidth, 'Drop point is on the left' - strictEqual blockView.dropPoint.y, + strictEqual blockView.dropPoints[0].x, view_.opts.indentWidth, 'Drop point is on the left' + strictEqual blockView.dropPoints[0].y, 1 * view_.opts.textHeight + 4 * view_.opts.padding + 2 * view_.opts.textPadding, 'Drop point is further down' @@ -821,7 +821,7 @@ require ['droplet-helper', 'droplet-model', 'droplet-parser', 'droplet-coffee', strictEqual blockView.carriageArrow, 0, 'Carriage arrow flag is set' - strictEqual blockView.dropPoint.x, view_.opts.indentWidth, 'Drop point is on the left' + strictEqual blockView.dropPoints[0].x, view_.opts.indentWidth, 'Drop point is on the left' indent = block.end.next.container indentView = view_.getViewNodeFor indent diff --git a/test/data/parserSuccess.json b/test/data/parserSuccess.json index ab6164ea..2cbbc79b 100644 --- a/test/data/parserSuccess.json +++ b/test/data/parserSuccess.json @@ -7,7 +7,7 @@ { "message": "Variable assignment", "str": "a = b", - "expected": "a<\/socket> = b<\/socket><\/block><\/segment>" + "expected": "a<\/socket> = b<\/socket><\/block><\/segment>" }, { "message": "If statement, normal form", @@ -77,12 +77,12 @@ { "message": "Object literal, normal form", "str": "foo {\n a: b,\n c: d\n}", - "expected": "foo<\/socket> {\n a<\/socket>: b<\/socket>,\n c<\/socket>: d<\/socket>\n}<\/block><\/socket><\/block><\/segment>" + "expected": "foo {\na: b,\nc: d\n}" }, { "message": "Object literal, no braces or commas", "str": "foo\n a: b\n c: d", - "expected": "foo<\/socket>\n a<\/socket>: b<\/socket>\n c<\/socket>: d<\/socket><\/block><\/socket><\/block><\/segment>" + "expected": "foo\na: b\nc: d" }, { "message": "String interpolation", @@ -97,6 +97,6 @@ { "message": "Array", "str": "[0, 1]", - "expected": "[0, <\/block>1<\/block><\/indent>]<\/block><\/segment>" + "expected": "[0, <\/block>1<\/block><\/indent>]<\/block><\/segment>" } ]