mirror of
https://github.com/paradoxxxzero/butterfly.git
synced 2026-05-26 07:08:08 +00:00
Well that was fun. (This web worker version is 3x slower but a bit smoother. Sadly it doesn't work in firefox because of this bug: https://bugzilla.mozilla.org/show_bug.cgi?id=504553)
This commit is contained in:
@@ -15,65 +15,786 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
worker = new Worker('/static/javascripts/worker.js')
|
||||
|
||||
worker.addEventListener 'message', (e) ->
|
||||
switch e.data.cmd
|
||||
when 'refresh'
|
||||
frontterm.refresh e.data.lines, e.data.start, e.data.end
|
||||
|
||||
cols = rows = null
|
||||
quit = false
|
||||
|
||||
$ = document.querySelectorAll.bind(document)
|
||||
|
||||
send = (data) ->
|
||||
ws.send 'S' + data
|
||||
worker.postMessage
|
||||
cmd: 'data'
|
||||
data: data
|
||||
|
||||
ctl = (type, args...) ->
|
||||
params = args.join(',')
|
||||
if type == 'Resize'
|
||||
ws.send 'R' + params
|
||||
worker.postMessage 'R' + params
|
||||
# ws.send 'R' + params
|
||||
|
||||
ws_url = 'ws://' + document.location.host + '/ws' + location.pathname
|
||||
ws = new WebSocket ws_url
|
||||
|
||||
ws.addEventListener 'open', ->
|
||||
console.log "WebSocket open", arguments
|
||||
ws.send 'R' + term.cols + ',' + term.rows
|
||||
if location.hash
|
||||
setTimeout ->
|
||||
ws.send 'S' + location.hash.slice(1) + '\n'
|
||||
, 100
|
||||
|
||||
ws.addEventListener 'error', ->
|
||||
console.log "WebSocket error", arguments
|
||||
|
||||
ws.addEventListener 'message', (e) ->
|
||||
setTimeout ->
|
||||
term.write e.data
|
||||
, 1
|
||||
|
||||
ws.addEventListener 'close', ->
|
||||
console.log "WebSocket closed", arguments
|
||||
quit = true
|
||||
open('','_self').close()
|
||||
|
||||
|
||||
term = new Terminal $('#wrapper')[0], send, ctl
|
||||
addEventListener 'beforeunload', ->
|
||||
if not quit
|
||||
'This will exit the terminal session'
|
||||
|
||||
bench = (n=100000000) ->
|
||||
rnd = ''
|
||||
while rnd.length < n
|
||||
rnd += Math.random().toString(36).substring(2)
|
||||
|
||||
t0 = (new Date()).getTime()
|
||||
term.write rnd
|
||||
console.log "#{n} chars in #{(new Date()).getTime() - t0} ms"
|
||||
cancel = (ev) ->
|
||||
ev.preventDefault() if ev.preventDefault
|
||||
ev.stopPropagation() if ev.stopPropagation
|
||||
ev.cancelBubble = true
|
||||
false
|
||||
|
||||
|
||||
cbench = (n=100000000) ->
|
||||
rnd = ''
|
||||
while rnd.length < n
|
||||
rnd += "\x1b[#{30 + parseInt(Math.random() * 20)}m"
|
||||
rnd += Math.random().toString(36).substring(2)
|
||||
class FrontTerminal
|
||||
constructor: (@parent) ->
|
||||
# Global elements
|
||||
@context = @parent.ownerDocument.defaultView
|
||||
@document = @parent.ownerDocument
|
||||
@body = @document.getElementsByTagName('body')[0]
|
||||
|
||||
t0 = (new Date()).getTime()
|
||||
term.write rnd
|
||||
console.log "#{n} chars + colors in #{(new Date()).getTime() - t0} ms"
|
||||
# Main terminal element
|
||||
@element = @document.createElement('div')
|
||||
@element.className = 'terminal focus'
|
||||
@element.style.outline = 'none'
|
||||
@element.setAttribute 'tabindex', 0
|
||||
|
||||
@parent.appendChild(@element)
|
||||
|
||||
# Adding one line to compute char size
|
||||
div = @document.createElement('div')
|
||||
div.className = 'line'
|
||||
@element.appendChild(div)
|
||||
@children = [div]
|
||||
|
||||
@compute_char_size()
|
||||
div.style.height = @char_size.height + 'px'
|
||||
term_size = @parent.getBoundingClientRect()
|
||||
@cols = Math.floor(term_size.width / @char_size.width) - 1 # ?
|
||||
@rows = Math.floor(term_size.height / @char_size.height)
|
||||
|
||||
i = @rows - 1
|
||||
while i--
|
||||
div = @document.createElement('div')
|
||||
div.style.height = @char_size.height + 'px'
|
||||
div.className = 'line'
|
||||
@element.appendChild(div)
|
||||
@children.push(div)
|
||||
|
||||
@visualBell = 100
|
||||
@cursorHidden = false
|
||||
@queue = ''
|
||||
|
||||
# stream
|
||||
@defAttr = (0 << 18) | (257 << 9) | (256 << 0)
|
||||
@skipNextKey = null
|
||||
@cursorState = 0
|
||||
|
||||
# Draw screen
|
||||
# @refresh 0, @rows - 1
|
||||
|
||||
@focus()
|
||||
|
||||
@startBlink()
|
||||
addEventListener 'keydown', @keyDown.bind(@)
|
||||
addEventListener 'keypress', @keyPress.bind(@)
|
||||
addEventListener 'focus', @focus.bind(@)
|
||||
addEventListener 'blur', @blur.bind(@)
|
||||
addEventListener 'paste', @paste.bind(@)
|
||||
addEventListener 'resize', @resize.bind(@)
|
||||
|
||||
# Horrible Firefox paste workaround
|
||||
if typeof InstallTrigger isnt "undefined"
|
||||
@element.contentEditable = 'true'
|
||||
@element.addEventListener "mouseup", (ev) =>
|
||||
sel = getSelection().getRangeAt(0)
|
||||
if sel.startOffset is sel.endOffset
|
||||
getSelection().removeAllRanges()
|
||||
|
||||
@initmouse()
|
||||
|
||||
compute_char_size: ->
|
||||
test_span = document.createElement('span')
|
||||
test_span.textContent = '0123456789'
|
||||
@children[0].appendChild(test_span)
|
||||
@char_size =
|
||||
width: test_span.getBoundingClientRect().width / 10
|
||||
height: @children[0].getBoundingClientRect().height
|
||||
@children[0].removeChild(test_span)
|
||||
|
||||
focus: ->
|
||||
@send('\x1b[I') if @sendFocus
|
||||
@showCursor()
|
||||
@element.classList.add('focus')
|
||||
@element.classList.remove('blur')
|
||||
|
||||
blur: ->
|
||||
@cursorState = 1
|
||||
# @refresh(@y, @y)
|
||||
@send('\x1b[O') if @sendFocus
|
||||
@element.classList.add('blur')
|
||||
@element.classList.remove('focus')
|
||||
|
||||
paste: (ev) ->
|
||||
if ev.clipboardData
|
||||
@send ev.clipboardData.getData('text/plain')
|
||||
else if @context.clipboardData
|
||||
@send @context.clipboardData.getData('Text')
|
||||
cancel(ev)
|
||||
|
||||
# XTerm mouse events
|
||||
# http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
|
||||
# To better understand these
|
||||
# the xterm code is very helpful:
|
||||
# Relevant files:
|
||||
# button.c, charproc.c, misc.c
|
||||
# Relevant functions in xterm/button.c:
|
||||
# BtnCode, EmitButtonCode, EditorButton, SendMousePosition
|
||||
initmouse: ->
|
||||
pressed = 32
|
||||
|
||||
# mouseup, mousedown, mousewheel
|
||||
# left click: ^[[M 3<^[[M#3<
|
||||
# mousewheel up: ^[[M`3>
|
||||
sendButton = (ev) =>
|
||||
# get the xterm-style button
|
||||
button = getButton(ev)
|
||||
|
||||
# get mouse coordinates
|
||||
pos = getCoords(ev)
|
||||
return unless pos
|
||||
sendEvent button, pos
|
||||
switch ev.type
|
||||
when "mousedown"
|
||||
pressed = button
|
||||
|
||||
when "mouseup"
|
||||
# keep it at the left
|
||||
# button, just in case.
|
||||
pressed = 32
|
||||
|
||||
# motion example of a left click:
|
||||
# ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7<
|
||||
sendMove = (ev) =>
|
||||
button = pressed
|
||||
pos = getCoords(ev)
|
||||
return unless pos
|
||||
|
||||
# buttons marked as motions
|
||||
# are incremented by 32
|
||||
button += 32
|
||||
sendEvent button, pos
|
||||
|
||||
# encode button and
|
||||
# position to characters
|
||||
encode = (data, ch) =>
|
||||
unless @utfMouse
|
||||
return data.push(0) if ch is 255
|
||||
ch = 127 if ch > 127
|
||||
data.push ch
|
||||
else
|
||||
return data.push(0) if ch is 2047
|
||||
if ch < 127
|
||||
data.push ch
|
||||
else
|
||||
ch = 2047 if ch > 2047
|
||||
data.push 0xC0 | (ch >> 6)
|
||||
data.push 0x80 | (ch & 0x3F)
|
||||
|
||||
|
||||
# send a mouse event:
|
||||
# regular/utf8: ^[[M Cb Cx Cy
|
||||
# urxvt: ^[[ Cb ; Cx ; Cy M
|
||||
# sgr: ^[[ Cb ; Cx ; Cy M/m
|
||||
# vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r
|
||||
# locator: CSI P e ; P b ; P r ; P c ; P p & w
|
||||
sendEvent = (button, pos) =>
|
||||
|
||||
if @urxvtMouse
|
||||
pos.x -= 32
|
||||
pos.y -= 32
|
||||
pos.x++
|
||||
pos.y++
|
||||
@send "\x1b[" + button + ";" + pos.x + ";" + pos.y + "M"
|
||||
return
|
||||
|
||||
if @sgrMouse
|
||||
pos.x -= 32
|
||||
pos.y -= 32
|
||||
@send "\x1b[<" + (if (button & 3) is 3 then button & ~3 else button) + ";" + pos.x + ";" + pos.y + (if (button & 3) is 3 then "m" else "M")
|
||||
return
|
||||
|
||||
data = []
|
||||
encode data, button
|
||||
encode data, pos.x
|
||||
encode data, pos.y
|
||||
@send "\x1b[M" + String.fromCharCode.apply(String, data)
|
||||
|
||||
getButton = (ev) =>
|
||||
# two low bits:
|
||||
# 0 = left
|
||||
# 1 = middle
|
||||
# 2 = right
|
||||
# 3 = release
|
||||
# wheel up/down:
|
||||
# 1, and 2 - with 64 added
|
||||
switch ev.type
|
||||
when "mousedown"
|
||||
button = if ev.button? then +ev.button else (if ev.which? then ev.which - 1 else null)
|
||||
when "mouseup"
|
||||
button = 3
|
||||
when "wheel"
|
||||
button = if ev.deltaY < 0 then 64 else 65
|
||||
|
||||
# next three bits are the modifiers:
|
||||
# 4 = shift, 8 = meta, 16 = control
|
||||
shift = if ev.shiftKey then 4 else 0
|
||||
meta = if ev.metaKey then 8 else 0
|
||||
ctrl = if ev.ctrlKey then 16 else 0
|
||||
mod = shift | meta | ctrl
|
||||
|
||||
# no mods
|
||||
if @vt200Mouse
|
||||
# ctrl only
|
||||
mod &= ctrl
|
||||
else
|
||||
mod = 0 unless @normalMouse
|
||||
|
||||
# increment to SP
|
||||
(32 + (mod << 2)) + button
|
||||
|
||||
# mouse coordinates measured in cols/rows
|
||||
getCoords = (ev) =>
|
||||
x = ev.pageX
|
||||
y = ev.pageY
|
||||
|
||||
# should probably check offsetParent
|
||||
# but this is more portable
|
||||
el = @element
|
||||
while el and el isnt @document.documentElement
|
||||
x -= el.offsetLeft
|
||||
y -= el.offsetTop
|
||||
el = if "offsetParent" of el then el.offsetParent else el.parentNode
|
||||
|
||||
# convert to cols/rows
|
||||
w = @element.clientWidth
|
||||
h = @element.clientHeight
|
||||
x = Math.ceil((x / w) * @cols)
|
||||
y = Math.ceil((y / h) * @rows)
|
||||
|
||||
# be sure to avoid sending
|
||||
# bad positions to the program
|
||||
x = 0 if x < 0
|
||||
x = @cols if x > @cols
|
||||
y = 0 if y < 0
|
||||
y = @rows if y > @rows
|
||||
|
||||
# xterm sends raw bytes and
|
||||
# starts at 32 (SP) for each.
|
||||
x += 32
|
||||
y += 32
|
||||
|
||||
x: x
|
||||
y: y
|
||||
type: ev.type
|
||||
|
||||
addEventListener "mousedown", (ev) =>
|
||||
return unless @mouseEvents
|
||||
|
||||
# send the button
|
||||
sendButton ev
|
||||
|
||||
# fix for odd bug
|
||||
#if (@vt200Mouse && !@normalMouse) {
|
||||
if @vt200Mouse
|
||||
sendButton
|
||||
__proto__: ev
|
||||
type: "mouseup"
|
||||
|
||||
return cancel(ev)
|
||||
|
||||
addEventListener "mousemove", sendMove.bind(this) if @normalMouse
|
||||
|
||||
# x10 compatibility mode can't send button releases
|
||||
unless @x10Mouse
|
||||
addEventListener "mouseup", up = (ev) =>
|
||||
sendButton ev
|
||||
removeEventListener "mousemove", sendMove if @normalMouse
|
||||
removeEventListener "mouseup", up
|
||||
cancel ev
|
||||
cancel ev
|
||||
|
||||
addEventListener "wheel", (ev) =>
|
||||
if @mouseEvents
|
||||
return if @x10Mouse
|
||||
sendButton ev
|
||||
else
|
||||
return if @applicationKeypad
|
||||
@scrollDisp if ev.deltaY > 0 then 5 else -5
|
||||
cancel ev
|
||||
|
||||
|
||||
refresh: (lines, start, end) ->
|
||||
if end - start >= @rows / 3
|
||||
parent = @element.parentNode
|
||||
parent?.removeChild @element
|
||||
|
||||
width = @cols
|
||||
y = start
|
||||
|
||||
if end >= lines.length
|
||||
end = lines.length - 1
|
||||
|
||||
while y <= end
|
||||
row = y
|
||||
line = lines[row]
|
||||
out = ""
|
||||
|
||||
if y is @y and (@ydisp is @ybase or @selectMode) and not @cursorHidden
|
||||
x = @x
|
||||
else
|
||||
x = -Infinity
|
||||
|
||||
attr = @defAttr
|
||||
i = 0
|
||||
while i < width
|
||||
data = line[i][0]
|
||||
ch = line[i][1]
|
||||
if data isnt attr
|
||||
out += "</span>" if attr isnt @defAttr
|
||||
if data isnt @defAttr
|
||||
classes = []
|
||||
out += "<span "
|
||||
bg = data & 0x1ff
|
||||
fg = (data >> 9) & 0x1ff
|
||||
flags = data >> 18
|
||||
|
||||
# bold
|
||||
classes.push "bold" if flags & 1
|
||||
# underline
|
||||
classes.push "underline" if flags & 2
|
||||
# blink
|
||||
classes.push "blink" if flags & 4
|
||||
# inverse
|
||||
classes.push "reverse-video" if flags & 8
|
||||
# invisible
|
||||
classes.push "invisible" if flags & 16
|
||||
|
||||
fg += 8 if flags & 1 and fg < 8
|
||||
classes.push "bg-color-" + bg
|
||||
classes.push "fg-color-" + fg
|
||||
|
||||
out += "class=\""
|
||||
out += classes.join(" ")
|
||||
out += "\">"
|
||||
out += "<span class=\"" + (if @cursorState then "reverse-video " else "") + "cursor\">" if i is x
|
||||
|
||||
# This is a temporary dirty hack for raw html insertion
|
||||
if ch.length > 1
|
||||
out += ch
|
||||
else
|
||||
switch ch
|
||||
when "&"
|
||||
out += "&"
|
||||
when "<"
|
||||
out += "<"
|
||||
when ">"
|
||||
out += ">"
|
||||
else
|
||||
if ch <= " "
|
||||
out += " "
|
||||
else
|
||||
i++ if "\uff00" < ch < "\uffef"
|
||||
out += ch
|
||||
out += "</span>" if i is x
|
||||
attr = data
|
||||
i++
|
||||
out += "</span>" if attr isnt @defAttr
|
||||
@children[y].innerHTML = out
|
||||
y++
|
||||
|
||||
parent?.appendChild @element
|
||||
|
||||
|
||||
_cursorBlink: ->
|
||||
@cursorState ^= 1
|
||||
cursor = @element.querySelector(".cursor")
|
||||
return unless cursor
|
||||
if cursor.classList.contains("reverse-video")
|
||||
cursor.classList.remove "reverse-video"
|
||||
else
|
||||
cursor.classList.add "reverse-video"
|
||||
|
||||
|
||||
showCursor: ->
|
||||
return
|
||||
# unless @cursorState
|
||||
# @cursorState = 1
|
||||
# @refresh @y, @y
|
||||
|
||||
|
||||
startBlink: ->
|
||||
return unless @cursorBlink
|
||||
@_blinker = => @_cursorBlink()
|
||||
@_blink = setInterval(@_blinker, 500)
|
||||
|
||||
|
||||
refreshBlink: ->
|
||||
return unless @cursorBlink
|
||||
clearInterval @_blink
|
||||
@_blink = setInterval(@_blinker, 500)
|
||||
|
||||
|
||||
scrollDisp: (disp) ->
|
||||
@ydisp += disp
|
||||
if @ydisp > @ybase
|
||||
@ydisp = @ybase
|
||||
|
||||
else
|
||||
@ydisp = 0 if @ydisp < 0
|
||||
|
||||
# @refresh 0, @rows - 1
|
||||
|
||||
keyDown: (ev) ->
|
||||
# Key Resources:
|
||||
# https://developer.mozilla.org/en-US/docs/DOM/KeyboardEvent
|
||||
# Don't handle modifiers alone
|
||||
return true if ev.keyCode > 15 and ev.keyCode < 19
|
||||
|
||||
# Handle shift insert and ctrl insert copy/paste usefull for typematrix keyboard
|
||||
return true if (ev.shiftKey or ev.ctrlKey) and ev.keyCode is 45
|
||||
|
||||
# Alt-z works as an escape to relay the following keys to the browser.
|
||||
# usefull to trigger browser shortcuts, i.e.: Alt+Z F5 to reload
|
||||
# May be redundant with keyPrefix
|
||||
if ev.altKey and ev.keyCode is 90 and not @skipNextKey
|
||||
@skipNextKey = true
|
||||
@element.classList.add('skip')
|
||||
return cancel(ev)
|
||||
|
||||
if @skipNextKey
|
||||
@skipNextKey = false
|
||||
@element.classList.remove('skip')
|
||||
return true
|
||||
|
||||
switch ev.keyCode
|
||||
# backspace
|
||||
when 8
|
||||
key = if ev.altKey then "\x1b" else ""
|
||||
if ev.shiftKey
|
||||
key += "\x08" # ^H
|
||||
break
|
||||
key += "\x7f" # ^?
|
||||
|
||||
# tab
|
||||
when 9
|
||||
if ev.shiftKey
|
||||
key = "\x1b[Z"
|
||||
break
|
||||
key = "\t"
|
||||
|
||||
# return/enter
|
||||
when 13
|
||||
key = "\r"
|
||||
|
||||
# escape
|
||||
when 27
|
||||
key = "\x1b"
|
||||
|
||||
# left-arrow
|
||||
when 37
|
||||
if @applicationCursor
|
||||
key = "\x1bOD" # SS3 as ^[O for 7-bit
|
||||
#key = '\x8fD'; // SS3 as 0x8f for 8-bit
|
||||
break
|
||||
return true if ev.shiftKey
|
||||
key = "\x1b[D"
|
||||
|
||||
# right-arrow
|
||||
when 39
|
||||
if @applicationCursor
|
||||
key = "\x1bOC"
|
||||
break
|
||||
return true if ev.shiftKey
|
||||
key = "\x1b[C"
|
||||
|
||||
# up-arrow
|
||||
when 38
|
||||
if @applicationCursor
|
||||
key = "\x1bOA"
|
||||
break
|
||||
if ev.ctrlKey
|
||||
@scrollDisp -1
|
||||
return cancel(ev)
|
||||
else if ev.shiftKey
|
||||
return true
|
||||
else
|
||||
key = "\x1b[A"
|
||||
|
||||
# down-arrow
|
||||
when 40
|
||||
if @applicationCursor
|
||||
key = "\x1bOB"
|
||||
break
|
||||
if ev.ctrlKey
|
||||
@scrollDisp 1
|
||||
return cancel(ev)
|
||||
else if ev.shiftKey
|
||||
return true
|
||||
else
|
||||
key = "\x1b[B"
|
||||
|
||||
# delete
|
||||
when 46
|
||||
key = "\x1b[3~"
|
||||
|
||||
# insert
|
||||
when 45
|
||||
key = "\x1b[2~"
|
||||
|
||||
# home
|
||||
when 36
|
||||
if @applicationKeypad
|
||||
key = "\x1bOH"
|
||||
break
|
||||
key = "\x1bOH"
|
||||
|
||||
# end
|
||||
when 35
|
||||
if @applicationKeypad
|
||||
key = "\x1bOF"
|
||||
break
|
||||
key = "\x1bOF"
|
||||
|
||||
# page up
|
||||
when 33
|
||||
if ev.shiftKey
|
||||
@scrollDisp -(@rows - 1)
|
||||
return cancel(ev)
|
||||
else
|
||||
key = "\x1b[5~"
|
||||
|
||||
# page down
|
||||
when 34
|
||||
if ev.shiftKey
|
||||
@scrollDisp @rows - 1
|
||||
return cancel(ev)
|
||||
else
|
||||
key = "\x1b[6~"
|
||||
|
||||
# F1
|
||||
when 112
|
||||
key = "\x1bOP"
|
||||
|
||||
# F2
|
||||
when 113
|
||||
key = "\x1bOQ"
|
||||
|
||||
# F3
|
||||
when 114
|
||||
key = "\x1bOR"
|
||||
|
||||
# F4
|
||||
when 115
|
||||
key = "\x1bOS"
|
||||
|
||||
# F5
|
||||
when 116
|
||||
key = "\x1b[15~"
|
||||
|
||||
# F6
|
||||
when 117
|
||||
key = "\x1b[17~"
|
||||
|
||||
# F7
|
||||
when 118
|
||||
key = "\x1b[18~"
|
||||
|
||||
# F8
|
||||
when 119
|
||||
key = "\x1b[19~"
|
||||
|
||||
# F9
|
||||
when 120
|
||||
key = "\x1b[20~"
|
||||
|
||||
# F10
|
||||
when 121
|
||||
key = "\x1b[21~"
|
||||
|
||||
# F11
|
||||
when 122
|
||||
key = "\x1b[23~"
|
||||
|
||||
# F12
|
||||
when 123
|
||||
key = "\x1b[24~"
|
||||
|
||||
else
|
||||
# a-z and space
|
||||
if ev.ctrlKey
|
||||
if ev.keyCode >= 65 and ev.keyCode <= 90
|
||||
|
||||
# Ctrl-A
|
||||
if @screenKeys
|
||||
if not @prefixMode and not @selectMode and ev.keyCode is 65
|
||||
@enterPrefix()
|
||||
return cancel(ev)
|
||||
|
||||
# Ctrl-V
|
||||
if @prefixMode and ev.keyCode is 86
|
||||
@leavePrefix()
|
||||
return
|
||||
|
||||
# Ctrl-C
|
||||
if (@prefixMode or @selectMode) and ev.keyCode is 67
|
||||
if @visualMode
|
||||
setTimeout (=>
|
||||
@leaveVisual()
|
||||
return
|
||||
), 1
|
||||
return
|
||||
key = String.fromCharCode(ev.keyCode - 64)
|
||||
else if ev.keyCode is 32
|
||||
|
||||
# NUL
|
||||
key = String.fromCharCode(0)
|
||||
else if ev.keyCode >= 51 and ev.keyCode <= 55
|
||||
|
||||
# escape, file sep, group sep, record sep, unit sep
|
||||
key = String.fromCharCode(ev.keyCode - 51 + 27)
|
||||
else if ev.keyCode is 56
|
||||
|
||||
# delete
|
||||
key = String.fromCharCode(127)
|
||||
else if ev.keyCode is 219
|
||||
|
||||
# ^[ - escape
|
||||
key = String.fromCharCode(27)
|
||||
|
||||
# ^] - group sep
|
||||
else
|
||||
key = String.fromCharCode(29) if ev.keyCode is 221
|
||||
|
||||
else if ev.altKey
|
||||
if ev.keyCode >= 65 and ev.keyCode <= 90
|
||||
key = "\x1b" + String.fromCharCode(ev.keyCode + 32)
|
||||
else if ev.keyCode is 192
|
||||
key = "\x1b`"
|
||||
else
|
||||
key = "\x1b" + (ev.keyCode - 48) if ev.keyCode >= 48 and ev.keyCode <= 57
|
||||
|
||||
if ev.keyCode >= 37 and ev.keyCode <= 40
|
||||
if ev.ctrlKey
|
||||
key = key.slice(0, -1) + "1;5" + key.slice(-1)
|
||||
else if ev.altKey
|
||||
key = key.slice(0, -1) + "1;3" + key.slice(-1)
|
||||
else key = key.slice(0, -1) + "1;4" + key.slice(-1) if ev.shiftKey
|
||||
|
||||
return true unless key
|
||||
|
||||
if @prefixMode
|
||||
@leavePrefix()
|
||||
return cancel(ev)
|
||||
|
||||
if @selectMode
|
||||
@keySelect ev, key
|
||||
return cancel(ev)
|
||||
|
||||
@showCursor()
|
||||
@handler(key)
|
||||
cancel ev
|
||||
|
||||
keyPress: (ev) ->
|
||||
if @skipNextKey is false
|
||||
@skipNextKey = null
|
||||
return true
|
||||
|
||||
cancel ev
|
||||
|
||||
if ev.charCode
|
||||
key = ev.charCode
|
||||
else unless ev.which?
|
||||
key = ev.keyCode
|
||||
else if ev.which isnt 0 and ev.charCode isnt 0
|
||||
key = ev.which
|
||||
else
|
||||
return false
|
||||
|
||||
return false if not key or ev.ctrlKey or ev.altKey or ev.metaKey
|
||||
key = String.fromCharCode(key)
|
||||
|
||||
@showCursor()
|
||||
@handler key
|
||||
false
|
||||
|
||||
handler: (data) ->
|
||||
worker.postMessage
|
||||
cmd: 'data',
|
||||
data: data
|
||||
|
||||
send: (data) ->
|
||||
unless @queue
|
||||
setTimeout (=>
|
||||
@handler @queue
|
||||
@queue = ""
|
||||
return
|
||||
), 1
|
||||
|
||||
@queue += data
|
||||
|
||||
bell: ->
|
||||
return unless @visualBell
|
||||
@element.classList.add "bell"
|
||||
setTimeout (=>
|
||||
@element.classList.remove "bell"
|
||||
), @visualBell
|
||||
|
||||
resize: ->
|
||||
old_cols = @cols
|
||||
old_rows = @rows
|
||||
term_size = @parent.getBoundingClientRect()
|
||||
@cols = Math.floor(term_size.width / @char_size.width) - 1 # ?
|
||||
@rows = Math.floor(term_size.height / @char_size.height)
|
||||
if old_cols == @cols and old_rows == @rows
|
||||
return
|
||||
|
||||
@ctl 'Resize', @cols, @rows
|
||||
|
||||
# resize rows
|
||||
j = old_rows
|
||||
if j < @rows
|
||||
el = @element
|
||||
while j++ < @rows
|
||||
if @children.length < @rows
|
||||
line = @document.createElement("div")
|
||||
line.className = 'line'
|
||||
line.style.height = @char_size.height + 'px'
|
||||
el.appendChild line
|
||||
@children.push line
|
||||
else if j > @rows
|
||||
while j-- > @rows
|
||||
if @children.length > @rows
|
||||
el = @children.pop()
|
||||
continue unless el
|
||||
el.parentNode.removeChild el
|
||||
|
||||
|
||||
get_html_height_in_lines: (html) ->
|
||||
temp_node = document.createElement("div")
|
||||
temp_node.innerHTML = html
|
||||
@element.appendChild temp_node
|
||||
html_height = temp_node.getBoundingClientRect().height
|
||||
@element.removeChild temp_node
|
||||
Math.ceil(html_height / @char_size.height)
|
||||
|
||||
|
||||
frontterm = new FrontTerminal $('#wrapper')[0], send, ctl
|
||||
worker.postMessage
|
||||
cmd: 'init'
|
||||
cols: frontterm.cols
|
||||
rows: frontterm.rows
|
||||
wsurl: 'ws://' + document.location.host + '/ws' + location.pathname
|
||||
|
||||
2303
butterfly/static/coffees/worker.coffee
Normal file
2303
butterfly/static/coffees/worker.coffee
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1712
butterfly/static/javascripts/worker.js
Normal file
1712
butterfly/static/javascripts/worker.js
Normal file
File diff suppressed because it is too large
Load Diff
2
dev.py
2
dev.py
@@ -8,7 +8,7 @@ import shlex
|
||||
|
||||
commands = [
|
||||
'coffee -wcb -j butterfly/static/javascripts/main.js ' +
|
||||
'butterfly/static/coffees/term.coffee ' +
|
||||
# 'butterfly/static/coffees/term.coffee ' +
|
||||
'butterfly/static/coffees/backsel.coffee ' +
|
||||
'butterfly/static/coffees/virtual_input.coffee ' +
|
||||
'butterfly/static/coffees/main.coffee ',
|
||||
|
||||
Reference in New Issue
Block a user