diff --git a/butterfly/sass/_layout.sass b/butterfly/sass/_layout.sass
index ac0637e..5b19c37 100644
--- a/butterfly/sass/_layout.sass
+++ b/butterfly/sass/_layout.sass
@@ -29,6 +29,34 @@ body
.line.active
background-color: $active-bg
+ .line.extended
+ overflow-x: auto
+ overflow-x: overlay
+ cursor: zoom-in
+ background-image: linear-gradient(90deg, rgba(darken($bg, 3%), 0), 95%, darken($bg, 3%))
+
+ &:not(.expanded):hover
+ background-color: lighten($bg, 2%)
+
+ &.expanded
+ cursor: zoom-out
+ background-color: darken($bg, 3%)
+
+ .extra
+ display: block
+ white-space: pre-line
+ word-break: break-all
+
+ &::-webkit-scrollbar
+ background: rgba($scroll-bg, .1)
+ height: 0
+
+ &::-webkit-scrollbar-thumb
+ background: rgba($scroll-fg, .1)
+
+ &::-webkit-scrollbar-thumb:hover
+ background: rgba($scroll-fg-hover, .1)
+
&::-webkit-scrollbar
background: $scroll-bg
width: $scroll-width
diff --git a/butterfly/static/ext.js b/butterfly/static/ext.js
index f1d975f..c77169d 100644
--- a/butterfly/static/ext.js
+++ b/butterfly/static/ext.js
@@ -156,6 +156,29 @@
}
});
+ Terminal.on('change', function(lines) {
+ var j, len1, line, results;
+ results = [];
+ for (j = 0, len1 = lines.length; j < len1; j++) {
+ line = lines[j];
+ if (indexOf.call(line.classList, 'extended') >= 0) {
+ results.push(line.addEventListener('click', (function(line) {
+ return function() {
+ if (indexOf.call(line.classList, 'expanded') >= 0) {
+ return line.classList.remove('expanded');
+ } else {
+ line.classList.add('expanded');
+ return butterfly.nativeScrollTo();
+ }
+ };
+ })(line)));
+ } else {
+ results.push(void 0);
+ }
+ }
+ return results;
+ });
+
walk = function(node, callback) {
var child, j, len1, ref, results;
ref = node.childNodes;
diff --git a/butterfly/static/main.css b/butterfly/static/main.css
index 036663b..949bc20 100644
--- a/butterfly/static/main.css
+++ b/butterfly/static/main.css
@@ -2816,6 +2816,27 @@ body {
/* Pop ups */ }
body .line.active {
background-color: transparent; }
+ body .line.extended {
+ overflow-x: auto;
+ overflow-x: overlay;
+ cursor: zoom-in;
+ background-image: linear-gradient(90deg, rgba(9, 8, 10, 0), 95%, #09080a); }
+ body .line.extended:not(.expanded):hover {
+ background-color: #161419; }
+ body .line.extended.expanded {
+ cursor: zoom-out;
+ background-color: #09080a; }
+ body .line.extended.expanded .extra {
+ display: block;
+ white-space: pre-line;
+ word-break: break-all; }
+ body .line.extended::-webkit-scrollbar {
+ background: rgba(17, 15, 19, 0.1);
+ height: 0; }
+ body .line.extended::-webkit-scrollbar-thumb {
+ background: rgba(244, 234, 213, 0.1); }
+ body .line.extended::-webkit-scrollbar-thumb:hover {
+ background: rgba(244, 234, 213, 0.1); }
body::-webkit-scrollbar {
background: #110f13;
width: 0.75em; }
diff --git a/butterfly/static/main.js b/butterfly/static/main.js
index 3f346a8..1269ddd 100644
--- a/butterfly/static/main.js
+++ b/butterfly/static/main.js
@@ -282,6 +282,7 @@
this.applicationCursor = false;
this.originMode = false;
this.autowrap = true;
+ this.horizontalWrap = false;
this.normal = null;
this.charset = null;
this.gcharset = null;
@@ -535,7 +536,7 @@
};
Terminal.prototype.refresh = function(force) {
- var active, attr, ch, classes, cursor, data, fg, group, i, j, k, len, len1, len2, len3, len4, line, lines, m, modified, newOut, o, out, q, ref, ref1, ref2, ref3, ref4, ref5, skipnext, styles, u, v, x;
+ var active, attr, ch, classes, cls, cursor, data, fg, group, i, j, k, len, len1, len2, len3, len4, line, lines, m, modified, newOut, o, out, q, ref, ref1, ref2, ref3, ref4, ref5, skipnext, styles, u, v, x;
if (force == null) {
force = false;
}
@@ -678,14 +679,27 @@
if (line.wrap) {
out += '\u23CE';
}
+ if (line.extra) {
+ out += '';
+ }
if (this.children[j]) {
this.children[j].innerHTML = out;
modified.push(this.children[j]);
if (x !== -Infinity) {
this.children[j].classList.add('active');
}
+ if (line.extra) {
+ this.children[j].classList.add('extended');
+ }
} else {
- newOut += "
" + out + "
";
+ cls = ['line'];
+ if (x !== -Infinity) {
+ cls.push('active');
+ }
+ if (line.extra) {
+ cls.push('extended');
+ }
+ newOut += "" + out + "
";
}
this.screen[j].dirty = false;
}
@@ -831,11 +845,17 @@
case "\n":
case "\x0b":
case "\x0c":
- this.screen[this.y + this.shift].dirty = true;
- this.nextLine();
+ if (this.horizontalWrap) {
+ this.screen[this.y + this.shift].extra += ch;
+ } else {
+ this.screen[this.y + this.shift].dirty = true;
+ this.nextLine();
+ }
break;
case "\r":
- this.x = 0;
+ if (!this.horizontalWrap) {
+ this.x = 0;
+ }
break;
case "\b":
if (this.x >= this.cols) {
@@ -877,11 +897,15 @@
ch = this.charset[ch];
}
if (this.x >= this.cols) {
- if (this.autowrap) {
- this.screen[this.y + this.shift].wrap = true;
- this.nextLine();
+ if (this.horizontalWrap) {
+ this.screen[this.y + this.shift].extra += ch;
+ } else {
+ if (this.autowrap) {
+ this.screen[this.y + this.shift].wrap = true;
+ this.nextLine();
+ }
+ this.x = 0;
}
- this.x = 0;
}
this.putChar(ch);
this.x++;
@@ -1291,8 +1315,7 @@
attr = this.cloneAttr(this.curAttr);
attr.html = "" + safe + "
";
this.screen[this.y + this.shift].chars[this.x] = attr;
- this.screen[this.y + this.shift].dirty = true;
- this.screen[this.y + this.shift].wrap = false;
+ this.resetLine(this.screen[this.y + this.shift]);
this.nextLine();
break;
case "IMAGE":
@@ -1307,8 +1330,7 @@
attr = this.cloneAttr(this.curAttr);
attr.html = "
";
this.screen[this.y + this.shift].chars[this.x] = attr;
- this.screen[this.y + this.shift].dirty = true;
- this.screen[this.y + this.shift].wrap = false;
+ this.resetLine(this.screen[this.y + this.shift]);
break;
case "PROMPT":
this.send(content);
@@ -1865,8 +1887,7 @@
line[x] = this.eraseAttr();
x++;
}
- this.screen[y + this.shift].dirty = true;
- return this.screen[y + this.shift].wrap = false;
+ return this.resetLine(this.screen[y + this.shift]);
};
Terminal.prototype.eraseLeft = function(x, y) {
@@ -1874,14 +1895,19 @@
while (x--) {
this.screen[y + this.shift].chars[x] = this.eraseAttr();
}
- this.screen[y + this.shift].dirty = true;
- return this.screen[y + this.shift].wrap = false;
+ return this.resetLine(this.screen[y + this.shift]);
};
Terminal.prototype.eraseLine = function(y) {
return this.eraseRight(0, y);
};
+ Terminal.prototype.resetLine = function(l) {
+ l.dirty = true;
+ l.wrap = false;
+ return l.extra = '';
+ };
+
Terminal.prototype.blankLine = function(cur, dirty) {
var attr, i, line;
if (cur == null) {
@@ -1900,7 +1926,8 @@
return {
chars: line,
dirty: dirty,
- wrap: false
+ wrap: false,
+ extra: ''
};
};
@@ -2304,8 +2331,7 @@
this.screen[this.y + this.shift].chars.splice(this.x, 1);
this.screen[this.y + this.shift].chars.push(this.eraseAttr());
}
- this.screen[this.y + this.shift].dirty = true;
- return this.screen[this.y + this.shift].wrap = false;
+ return this.resetLine(this.screen[this.y + this.shift]);
};
Terminal.prototype.eraseChars = function(params) {
@@ -2318,8 +2344,7 @@
while (param-- && j < this.cols) {
this.screen[this.y + this.shift].chars[j++] = this.eraseAttr();
}
- this.screen[this.y + this.shift].dirty = true;
- return this.screen[this.y + this.shift].wrap = false;
+ return this.resetLine(this.screen[this.y + this.shift]);
};
Terminal.prototype.charPosAbsolute = function(params) {
@@ -2455,6 +2480,8 @@
return this.autowrap = true;
case 66:
return this.applicationKeypad = true;
+ case 77:
+ return this.horizontalWrap = true;
case 9:
case 1000:
case 1002:
@@ -2534,6 +2561,8 @@
return this.autowrap = false;
case 66:
return this.applicationKeypad = false;
+ case 77:
+ return this.horizontalWrap = false;
case 9:
case 1000:
case 1002:
@@ -2834,8 +2863,7 @@
while (i < l) {
this.screen[i].chars.splice(this.x, 1);
this.screen[i].chars.push(this.eraseAttr());
- this.screen[i].dirty = true;
- this.screen[i].wrap = false;
+ this.resetLine(this.screen[i].dirty);
results1.push(i++);
}
return results1;
diff --git a/coffees/term.coffee b/coffees/term.coffee
index 7be84a1..d7ef5fe 100644
--- a/coffees/term.coffee
+++ b/coffees/term.coffee
@@ -168,6 +168,7 @@ class Terminal
@applicationCursor = false
@originMode = false
@autowrap = true
+ @horizontalWrap = false
@normal = null
# charset
@@ -421,6 +422,8 @@ class Terminal
@document.createTextNode(cursor.textContent), cursor)
for active in @body.querySelectorAll(".line.active")
active.classList.remove('active')
+ # for active in @body.querySelectorAll(".line.extended")
+ # active.classList.remove('extended')
newOut = ''
modified = []
@@ -526,14 +529,22 @@ class Terminal
attr = data
out += "" unless @equalAttr attr, @defAttr
out += '\u23CE' if line.wrap
+ if line.extra
+ out += ''
if @children[j]
@children[j].innerHTML = out
modified.push @children[j]
if x isnt -Infinity
@children[j].classList.add 'active'
+ if line.extra
+ @children[j].classList.add 'extended'
else
- newOut += "#{out}
"
+ cls = ['line']
+ if x isnt -Infinity
+ cls.push 'active'
+ if line.extra
+ cls.push 'extended'
+ newOut += "#{out}
"
@screen[j].dirty = false
if newOut isnt ''
@@ -644,12 +655,16 @@ class Terminal
# '\n', '\v', '\f'
when "\n", "\x0b", "\x0c"
# @x = 0 if @convertEol
- @screen[@y + @shift].dirty = true
- @nextLine()
+ if @horizontalWrap
+ @screen[@y + @shift].extra += ch
+ else
+ @screen[@y + @shift].dirty = true
+ @nextLine()
# '\r'
when "\r"
- @x = 0
+ unless @horizontalWrap
+ @x = 0
# '\b'
when "\b"
@@ -697,11 +712,13 @@ class Terminal
if ch >= " "
ch = @charset[ch] if @charset?[ch]
if @x >= @cols
- if @autowrap
- @screen[@y + @shift].wrap = true
- @nextLine()
- @x = 0
-
+ if @horizontalWrap
+ @screen[@y + @shift].extra += ch
+ else
+ if @autowrap
+ @screen[@y + @shift].wrap = true
+ @nextLine()
+ @x = 0
@putChar ch
@x++
if @forceWidth and "\uff00" < ch < "\uffef"
@@ -1150,8 +1167,6 @@ class Terminal
switch @prefix
# User-Defined Keys (DECUDK).
when ""
- # Disabling this for now as we need a good script
- # striper to avoid malicious script injection
pt = @currentParam
unless pt[0] is ';'
console.error "Unknown DECUDK: #{pt}"
@@ -1171,8 +1186,7 @@ class Terminal
attr.html = (
"#{safe}
")
@screen[@y + @shift].chars[@x] = attr
- @screen[@y + @shift].dirty = true
- @screen[@y + @shift].wrap = false
+ @resetLine @screen[@y + @shift]
@nextLine()
when "IMAGE"
@@ -1190,8 +1204,7 @@ class Terminal
"
")
@screen[@y + @shift].chars[@x] = attr
- @screen[@y + @shift].dirty = true
- @screen[@y + @shift].wrap = false
+ @resetLine @screen[@y + @shift]
when "PROMPT"
@send content
@@ -1690,18 +1703,21 @@ class Terminal
while x < @cols
line[x] = @eraseAttr()
x++
- @screen[y + @shift].dirty = true
- @screen[y + @shift].wrap = false
+ @resetLine @screen[y + @shift]
eraseLeft: (x, y) ->
x++
@screen[y + @shift].chars[x] = @eraseAttr() while x--
- @screen[y + @shift].dirty = true
- @screen[y + @shift].wrap = false
+ @resetLine @screen[y + @shift]
eraseLine: (y) ->
@eraseRight 0, y
+ resetLine: (l) ->
+ l.dirty = true
+ l.wrap = false
+ l.extra = ''
+
blankLine: (cur=false, dirty=true) ->
attr = (if cur then @eraseAttr() else @defAttr)
line = []
@@ -1713,6 +1729,7 @@ class Terminal
chars: line
dirty: dirty
wrap: false
+ extra: ''
ch: (cur) ->
if cur then @eraseAttr() else @defAttr
@@ -2193,8 +2210,7 @@ class Terminal
while param--
@screen[@y + @shift].chars.splice @x, 1
@screen[@y + @shift].chars.push @eraseAttr()
- @screen[@y + @shift].dirty = true
- @screen[@y + @shift].wrap = false
+ @resetLine @screen[@y + @shift]
# CSI Ps X
# Erase Ps Character(s) (default = 1) (ECH).
@@ -2204,8 +2220,7 @@ class Terminal
j = @x
# xterm
@screen[@y + @shift].chars[j++] = @eraseAttr() while param-- and j < @cols
- @screen[@y + @shift].dirty = true
- @screen[@y + @shift].wrap = false
+ @resetLine @screen[@y + @shift]
# CSI Pm ` Character Position Absolute
# [column] (default = [row,1]) (HPA).
@@ -2434,6 +2449,8 @@ class Terminal
@autowrap = true
when 66
@applicationKeypad = true
+ when 77
+ @horizontalWrap = true
# X10 Mouse
# no release, no motion, no wheel, no modifiers.
when 9, 1000, 1002, 1003 # any event mouse
@@ -2595,6 +2612,8 @@ class Terminal
@autowrap = false
when 66
@applicationKeypad = false
+ when 77
+ @horizontalWrap = false
when 9, 1000, 1002 , 1003 # any event mouse
@x10Mouse = false
@vt200Mouse = false
@@ -3175,8 +3194,7 @@ class Terminal
while i < l
@screen[i].chars.splice @x, 1
@screen[i].chars.push @eraseAttr()
- @screen[i].dirty = true
- @screen[i].wrap = false
+ @resetLine @screen[i].dirty
i++
# DEC Special Character and Line Drawing Set.