return false
return found # maybe null
+find_up_cursor_position = (tree, cursor, ideal_x) ->
+ new_cursor = cursor
+ # go prev until we're higher on y axis
+ while new_cursor.y >= cursor.y
+ new_cursor = find_prev_cursor_position tree, new_cursor
+ return null unless new_cursor?
+ # done early if we're already left of old cursor position
+ if new_cursor.x <= ideal_x
+ return new_cursor
+ target_y = new_cursor.y
+ # search leftward, until we find the closest position
+ # new_cursor is the prev-most position we've checked
+ # prev_cursor is the older value, so it's not as prev as new_cursor
+ while new_cursor.x > ideal_x and new_cursor.y is target_y
+ prev_cursor = new_cursor
+ new_cursor = find_prev_cursor_position tree, new_cursor
+ break unless new_cursor?
+ # move cursor to prev_cursor or new_cursor
+ if new_cursor?
+ if new_cursor.y is target_y
+ # both valid, and on the same line, use closest
+ if (ideal_x - new_cursor.x) < (prev_cursor.x - ideal_x)
+ return new_cursor
+ else
+ return prev_cursor
+ else
+ # new_cursor on wrong line, use prev_cursor
+ return prev_cursor
+ else
+ # can't go any further prev, use prev_cursor
+ return prev_cursor
+
+find_down_cursor_position = (tree, cursor, ideal_x) ->
+ new_cursor = cursor
+ # go next until we move on the y axis
+ while new_cursor.y <= cursor.y
+ new_cursor = find_next_cursor_position tree, new_cursor
+ return null unless new_cursor?
+ # done early if we're already right of old cursor position
+ if new_cursor.x >= ideal_x
+ # this would be strange, but could happen due to runaround
+ return new_cursor
+ target_y = new_cursor.y
+ # search rightward, until we find the closest position
+ # new_cursor is the next-most position we've checked
+ # prev_cursor is the older value, so it's not as next as new_cursor
+ while new_cursor.x < ideal_x and new_cursor.y is target_y
+ prev_cursor = new_cursor
+ new_cursor = find_next_cursor_position tree, new_cursor
+ break unless new_cursor?
+ # move cursor to prev_cursor or new_cursor
+ if new_cursor?
+ if new_cursor.y is target_y
+ # both valid, and on the same line, use closest
+ if (new_cursor.x - ideal_x) < (ideal_x - prev_cursor.x)
+ return new_cursor
+ else
+ return prev_cursor
+ else
+ # new_cursor on wrong line, use prev_cursor
+ return prev_cursor
+ else
+ # can't go any further prev, use prev_cursor
+ return prev_cursor
+
xy_to_cursor = (tree, xy) ->
for n in tree
if n.type is 'tag' or n.type is 'text'
@cursor = null
@cursor_el = null
@cursor_visible = false
+ @cursor_ideal_x = null
@poll_for_blur_timeout = null
opt_fragment = @options.fragment ? true
@parser_opts = {}
return false
when KEY_UP
if @cursor?
- new_cursor = @cursor
- # go prev until we're higher on y axis
- while new_cursor.y >= @cursor.y
- new_cursor = find_prev_cursor_position @tree, new_cursor
- return false unless new_cursor?
- # done early if we're already left of old cursor position
- if new_cursor.x <= @cursor.x
- @move_cursor new_cursor
- return false
- target_y = new_cursor.y
- # search leftward, until we find the closest position
- # new_cursor is the prev-most position we've checked
- # prev_cursor is the older value, so it's not as prev as new_cursor
- while new_cursor.x > @cursor.x and new_cursor.y is target_y
- prev_cursor = new_cursor
- new_cursor = find_prev_cursor_position @tree, new_cursor
- break unless new_cursor?
- # move cursor to prev_cursor or new_cursor
+ new_cursor = find_up_cursor_position @tree, @cursor, @cursor_ideal_x
if new_cursor?
- if new_cursor.y is target_y
- # both valid, and on the same line, use closest
- if (@cursor.x - new_cursor.x) < (prev_cursor.x - @cursor.x)
- @move_cursor new_cursor
- else
- @move_cursor prev_cursor
- else
- # new_cursor on wrong line, use prev_cursor
- @move_cursor prev_cursor
- else
- # can't go any further prev, use prev_cursor
- @move_cursor prev_cursor
+ saved_ideal_x = @cursor_ideal_x
+ @move_cursor new_cursor
+ @cursor_ideal_x = saved_ideal_x
else
# move cursor to first position in document
new_cursor = first_cursor_position @tree
return false
when KEY_DOWN
if @cursor?
- new_cursor = @cursor
- # go next until we move on the y axis
- while new_cursor.y <= @cursor.y
- new_cursor = find_next_cursor_position @tree, new_cursor
- return false unless new_cursor?
- # done early if we're already right of old cursor position
- if new_cursor.x >= @cursor.x
- # this would be strange, but could happen due to runaround
- @move_cursor new_cursor
- return false
- target_y = new_cursor.y
- # search rightward, until we find the closest position
- # new_cursor is the next-most position we've checked
- # prev_cursor is the older value, so it's not as next as new_cursor
- while new_cursor.x < @cursor.x and new_cursor.y is target_y
- prev_cursor = new_cursor
- new_cursor = find_next_cursor_position @tree, new_cursor
- break unless new_cursor?
- # move cursor to prev_cursor or new_cursor
+ new_cursor = find_down_cursor_position @tree, @cursor, @cursor_ideal_x
if new_cursor?
- if new_cursor.y is target_y
- # both valid, and on the same line, use closest
- if (new_cursor.x - @cursor.x) < (@cursor.x - prev_cursor.x)
- @move_cursor new_cursor
- else
- @move_cursor prev_cursor
- else
- # new_cursor on wrong line, use prev_cursor
- @move_cursor prev_cursor
- else
- # can't go any further prev, use prev_cursor
- @move_cursor prev_cursor
+ saved_ideal_x = @cursor_ideal_x
+ @move_cursor new_cursor
+ @cursor_ideal_x = saved_ideal_x
else
# move cursor to first position in document
new_cursor = last_cursor_position @tree
@move_cursor new_cursor
return false
when KEY_END
+ new_cursor = last_cursor_position @tree
+ if new_cursor?
+ @move_cursor new_cursor
return false
when KEY_BACKSPACE
return false unless @cursor?
@kill_cursor()
return false
when KEY_ENTER
+ @on_key_enter e
return false
when KEY_ESCAPE
+ @kill_cursor()
return false
when KEY_HOME
+ new_cursor = first_cursor_position @tree
+ if new_cursor?
+ @move_cursor new_cursor
return false
when KEY_INSERT
return false
console.log "ERROR: couldn't find cursor position after insert"
@kill_cursor()
return false
+ on_key_enter: (e) -> # enter key pressed
+ return unless @cursor_visible
+ cur_block = @cursor.n
+ loop
+ if cur_block.type is 'tag'
+ if is_display_block cur_block.el
+ break
+ return unless cur_block.parent?
+ cur_block = cur_block.parent
+ # find array to insert new element into
+ if cur_block.parent?.el?
+ parent_el = cur_block.parent.el
+ pc = cur_block.parent.children
+ else
+ parent_el = @idoc.body
+ pc = @tree
+ # find index of current block in its parent
+ for n, i in pc
+ break if n is cur_block
+ i += 1 # we want to be after it
+ if i < pc.length
+ before = pc[i].el
+ else
+ before = null
+ # TODO if content after cursor
+ # TODO new block is empty
+ new_text = new peach_parser.Node 'text', text: ' '
+ new_node = new peach_parser.Node 'tag', name: 'p', parent: cur_block.parent, attrs: {style: 'white-space: pre-wrap'}, children: [new_text]
+ new_text.parent = new_node
+ new_text.el = domify @idoc, text: ' '
+ new_node.el = domify @idoc, p: style: 'white-space: pre-wrap', children: [new_text.el]
+ pc.splice i, 0, new_node
+ parent_el.insertBefore new_node.el, before
+ new_cursor = new_cursor_position n: new_text, i: 0
+ throw 'bork bork' unless new_cursor?
+ @move_cursor new_cursor
+ # TODO move content past cursor into this new block
clear_dom: -> # remove all the editable content (and cursor, overlays, etc)
while @idoc.body.childNodes.length
@idoc.body.removeChild @idoc.body.childNodes[0]
return unless parent.el?
# insert the character
if i is 0
- n.text = char + n.text
+ # special case: typing before a space that's the only thing in a block/doc
+ # reason: enter key creates blocks with just a space in them
+ special_case = false
+ if n.text is ' '
+ if n.parent?.el?
+ if n.parent.children.length is 1
+ if n.parent.children[0] is n
+ special_case = true
+ else
+ special_case = true
+ if special_case
+ n.text = char
+ else
+ n.text = char + n.text
else if i is n.text.length
+ # replace the space
n.text += char
else
n.text =
@cursor = null
@annotate null
move_cursor: (cursor) ->
+ @cursor_ideal_x = cursor.x
@cursor = cursor
unless @cursor_visible
@cursor_el = domify @outer_idoc, div: id: 'cursor'