ret = ret.substr 0, clip + 1
return ret
+# table too look up the properties of various values for css's white-space
+ws_props =
+ normal:
+ space: false # spaces are not preserved/rendered
+ newline: false # newlines are not preserved/rendered
+ wrap: true # text is word-wrapped
+ to_preserve: 'pre-wrap' # to preservespaces, change white-space to this
+ nowrap:
+ space: false
+ newline: false
+ wrap: false
+ to_preserve: 'pre'
+ 'pre-line':
+ space: false
+ newline: true
+ wrap: true
+ to_preserve: 'pre-wrap'
+ pre:
+ space: true
+ newline: true
+ wrap: false
+ 'pre-wrap':
+ space: true
+ newline: true
+ wrap: true
+
# xml 1.0 spec, chromium and firefox accept these, plus lots of unicode chars
valid_attr_regex = new RegExp '^[a-zA-Z_:][-a-zA-Z0-9_:.]*$'
# html5 spec is much more lax, but chromium won't let me make at attribute with the name "4"
onkeypress: (e) ->
return if e.ctrlKey
return false if ignore_key_codes[e.keyCode]?
- # return false if control_key_codes[e.keyCode]? # handled in keydown
char = e.charCode ? e.keyCode
if char and @cursor?
char = String.fromCharCode char
- if @cursor.i is 0
- @cursor.n.text = char + @cursor.n.text
- else if @cursor.i is @cursor.n.text.length - 1
- @cursor.n.text += char
- else
- @cursor.n.text =
- @cursor.n.text.substr(0, @cursor.i) +
- char +
- @cursor.n.text.substr(@cursor.i)
- @cursor.n.el.nodeValue = @cursor.n.text
+ @insert_character @cursor.n, @cursor.i, char
new_cursor = new_cursor_position n: @cursor.n, i: @cursor.i + 1
- unless new_cursor
- # probably pressed space, and browser isn't displaying it
- # FIXME insert instead, rip it out later if possible, etc.
- # for now, remove it
- @cursor.n.text =
- @cursor.n.text.substr(0, @cursor.i) +
- @cursor.n.text.substr(@cursor.i + 1)
- @cursor.n.el.nodeValue = @cursor.n.text
- return false
- @move_cursor new_cursor
+ if new_cursor
+ @move_cursor new_cursor
+ else
+ console.log "ERROR: couldn't find cursor position after insert"
+ @kill_cursor()
@changed()
return false
clear_dom: -> # remove all the editable content (and cursor, overlays, etc)
@iframe.style.height = "0"
@iframe.style.height = "#{h}px"
@wrap2.scrollTop = s
+ # Warning: this does not call changed() for you
+ insert_character: (n, i, char) ->
+ # TODO handle newlines, tabs, etc
+ parent = @cursor.n.parent
+ return unless parent
+ return unless parent.el?
+ style = @iframe.contentWindow.getComputedStyle parent.el, null
+ ws = style.getPropertyValue 'white-space'
+ if char is ' '
+ unless ws_props[ws].space
+ change = false
+ if i is 0
+ # TODO check if a space at the beginning would actually get collapsed
+ change = true
+ else if i is n.text.length
+ change = true
+ # TODO check if a space at the end would actually get collapsed
+ else
+ if n.text.charAt(i - 1) is ' ' or n.text.charAt(i) is ' '
+ change = true
+ if change
+ rule = "white-space: #{ws_props[ws].to_preserve}"
+ if parent.attrs[style]?
+ parent.attrs.style += "; #{rule}"
+ else
+ parent.attrs.style = rule
+ parent.el.setAttribute 'style', parent.attrs.style
+ else
+ # TODO test this
+ # inserting a visible (non-space) character
+ if ws_props[ws].space
+ if parent.el.style?['white-space']
+ # This node has a "white-space" property on it
+ # probably created automatically by this editor
+ # when the user pressed space.
+ # Check if that's no longer needed.
+ need = false
+ for ti in [0...n.text.length]
+ code = n.text.charCodeAt ti
+ if code isnt 32 and is_space_code code
+ # tab, return
+ need = true
+ break
+ # check for double spaces that don't surround insert location
+ continue if ti is i
+ continue if ti is 0
+ if n.text.substr(ti - 1, 2) is ' '
+ need = true
+ break
+ if i > 0
+ if 32 is n.text.charCodeAt 0
+ need = true
+ if i < n.text.length
+ if 32 is n.text.charCodeAt n.text.length - 1
+ need = true
+ unless need
+ # TODO don't assume whitespace is just so
+ if parent.attrs.style is "white-space: #{ws}"
+ delete parent.attrs.style
+ parent.el.removeAttribute 'style'
+ else
+ # FIXME find it in the middle and at the start
+ needle = "; white-space: #{ws}"
+ if needle is parent.attrs.style.substr parent.attrs.style.length - needle
+ parent.attrs.style = parent.attrs.style.substr 0, parent.attrs.style.length - needle
+ parent.el.setAttribute parent.attrs.style
+ # TODO insert the character now
+ if i is 0
+ n.text = char + n.text
+ else if i is n.text.length - 1
+ n.text += char
+ else
+ n.text =
+ n.text.substr(0, i) +
+ char +
+ n.text.substr(i)
+ n.el.nodeValue = n.text
+ # TODO call this when the user types
+ # TODO detect when typing produces a collapsing space
+ remove_character: (n, i) ->
+ # TODO call this from delete and backspace key handlers
+ # TODO detect if this would result in collapsing space
kill_cursor: -> # remove it, forget where it was
if @cursor_visible
@cursor_el.parentNode.removeChild @cursor_el