+ # 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