From 670b0d58c76ec9f8c0b1c7bb314cea606cbbd97c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Aug 2016 12:42:10 -0700 Subject: [PATCH 01/16] Add base viewport, set line height Fixes #149 --- src/xterm.css | 15 +++++++-------- src/xterm.js | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/xterm.css b/src/xterm.css index 07b82dd5..93286892 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -103,6 +103,13 @@ display: block; } +.terminal .xterm-char-measure-element { + display: inline-block; + visibility: hidden; + position: absolute; + left: -9999em; +} + /* * Determine default colors for xterm.js */ @@ -2169,11 +2176,3 @@ .terminal .xterm-bg-color-255 { background-color: #eeeeee; } - -/** - * All terminal rows should have explicitly declared height, - * in order to allow child elements to adjust. - */ -.terminal .xterm-rows > div { - line-height: normal; -} diff --git a/src/xterm.js b/src/xterm.js index 01233494..a1ac3a01 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -324,6 +324,24 @@ this.textarea.style.top = ''; } + function Viewport(terminal, viewportElement, charMeasureElement) { + this.terminal = terminal; + this.viewportElement = viewportElement; + this.charMeasureElement = charMeasureElement; + this.currentHeight = 0; + + this.terminal.on('refresh', this.refreshRowHeight.bind(this)); + } + + Viewport.prototype.refreshRowHeight = function() { + var size = this.charMeasureElement.getBoundingClientRect(); + if (size.height > 0 && size.height !== this.currentHeight) { + this.currentHeight = size.height; + this.terminal.rowContainer.style.lineHeight = size.height + 'px'; + this.terminal.rowContainer.style.height = size.height * this.terminal.rows; + } + } + /** * States */ @@ -787,7 +805,6 @@ return row; }; - /** * Opens the terminal within an element. * @@ -841,15 +858,21 @@ this.element.classList.add('terminal'); this.element.classList.add('xterm'); this.element.classList.add('xterm-theme-' + this.theme); + + this.element.style.height this.element.setAttribute('tabindex', 0); + this.viewportElement = document.createElement('div'); + this.viewportElement.classList.add('xterm-viewport'); + this.element.appendChild(this.viewportElement); + /* * Create the container that will hold the lines of the terminal and then * produce the lines the lines. */ this.rowContainer = document.createElement('div'); this.rowContainer.classList.add('xterm-rows'); - this.element.appendChild(this.rowContainer); + this.viewportElement.appendChild(this.rowContainer); this.children = []; /* @@ -879,6 +902,13 @@ this.compositionHelper = new CompositionHelper(this.textarea, this.compositionView, this); this.helperContainer.appendChild(this.compositionView); + this.charMeasureElement = document.createElement('div'); + this.charMeasureElement.classList.add('xterm-char-measure-element'); + this.charMeasureElement.innerHTML = 'W'; + this.helperContainer.appendChild(this.charMeasureElement); + + this.viewport = new Viewport(this, this.viewportElement, this.charMeasureElement); + for (; i < this.rows; i++) { this.insertRow(); } @@ -1367,7 +1397,7 @@ if (end - start >= this.rows / 2) { parent = this.element.parentNode; if (parent) { - this.element.removeChild(this.rowContainer); + this.viewportElement.removeChild(this.rowContainer); } } @@ -1517,7 +1547,7 @@ } if (parent) { - this.element.appendChild(this.rowContainer); + this.viewportElement.appendChild(this.rowContainer); } this.emit('refresh', {element: this.element, start: start, end: end}); From ff927b8e79851bfa29429cafa9ef599e93ae64f2 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Aug 2016 16:59:50 -0700 Subject: [PATCH 02/16] Implement basic scroll bar --- src/xterm.css | 14 ++++++++++ src/xterm.js | 73 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/src/xterm.css b/src/xterm.css index 93286892..f18ef3d7 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -103,6 +103,20 @@ display: block; } +.terminal .xterm-viewport { + overflow-y: scroll; +} + +.terminal .xterm-rows { + position: absolute; + left: 0; + top: 0; +} + +.terminal .xterm-scroll-area { + visibility: hidden; +} + .terminal .xterm-char-measure-element { display: inline-block; visibility: hidden; diff --git a/src/xterm.js b/src/xterm.js index a1ac3a01..58e1ae44 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -328,19 +328,78 @@ this.terminal = terminal; this.viewportElement = viewportElement; this.charMeasureElement = charMeasureElement; + this.scrollArea = document.createElement('div'); + this.scrollArea.classList.add('xterm-scroll-area'); + this.viewportElement.appendChild(this.scrollArea); this.currentHeight = 0; + this.lastScrollPosition; this.terminal.on('refresh', this.refreshRowHeight.bind(this)); + // TODO: Attach this to a more sensible event + this.terminal.on('refresh', this.syncScrollArea.bind(this)); + this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); } Viewport.prototype.refreshRowHeight = function() { var size = this.charMeasureElement.getBoundingClientRect(); if (size.height > 0 && size.height !== this.currentHeight) { this.currentHeight = size.height; - this.terminal.rowContainer.style.lineHeight = size.height + 'px'; - this.terminal.rowContainer.style.height = size.height * this.terminal.rows; + this.viewportElement.style.lineHeight = size.height + 'px'; + this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; } - } + // TODO: Should this be lines.length - ybase? or lines.length - rows? + this.scrollArea.style.height = (size.height * this.terminal.lines.length) + 'px'; + }; + + Viewport.prototype.syncScrollArea = function() { + //console.log('ybase: ' + this.terminal.ybase); + //console.log('ydisp: ' + this.terminal.ydisp); + this.viewportElement.scrollTop = this.terminal.ydisp * this.currentHeight; + }; + + Viewport.prototype.onScroll = function(ev) { + console.log('onScroll', ev); + console.log('this.viewportElement.scrollTop: ' + this.viewportElement.scrollTop); + console.log('row: ' + Math.round(this.viewportElement.scrollTop / this.currentHeight)); + console.log('lastScrollPosition: ' + this.lastScrollPosition); + console.log(' scrollTop: ' + this.viewportElement.scrollTop); + + // This helps get around the case where scrollTop changes by 1 pixel by pressing up or down on the scrollbar + // It gets complicated as the scrollbar sometimes locks to a multiple of the row + if (this.lastScrollPosition !== this.viewportElement.scrollTop) { + console.log('in if'); + this.lastScrollPosition = this.viewportElement.scrollTop; + var newRow = this.viewportElement.scrollTop / this.currentHeight; + var diff = newRow - this.terminal.ydisp; + if (diff === 0) { + console.log('diff = 0'); + ev.preventDefault(); + ev.stopPropagation(); + return; + } + var multiplier = diff < 0 ? -1 : 1; + diff = Math.max(1, Math.round(Math.abs(diff))) * multiplier; + console.log('onScroll diff: ' + diff); + this.terminal.scrollDisp(diff); + } + ev.preventDefault(); + ev.stopPropagation(); + }; + + Viewport.prototype.onWheel = function(ev) { + //var newRow = this.viewportElement.scrollTop / this.currentHeight; + //console.log('ydisp: ' + this.terminal.ydisp); + //console.log('this.viewportElement.scrollTop: ' + this.viewportElement.scrollTop); + //console.log('this.currentHeight: ' + this.currentHeight); + //console.log(ev); + //this.terminal.scrollDisp(this.terminal.ydisp - newRow); + var multiplier = ev.deltaY < 0 ? -1 : 1; + var diff = Math.max(1, Math.round(Math.abs(ev.deltaY / this.currentHeight))); + + console.log('diff * multiplier: ' + diff * multiplier); + this.terminal.scrollDisp(diff * multiplier); + //this.lastScrollPosition = this.viewportElement.scrollTop; + }; /** * States @@ -907,6 +966,7 @@ this.charMeasureElement.innerHTML = 'W'; this.helperContainer.appendChild(this.charMeasureElement); + this.viewport = new Viewport(this, this.viewportElement, this.charMeasureElement); for (; i < this.rows; i++) { @@ -1294,11 +1354,12 @@ on(el, wheelEvent, function(ev) { if (self.mouseEvents) return; if (self.applicationKeypad) return; - if (ev.type === 'DOMMouseScroll') { + self.viewport.onWheel(ev); + /*if (ev.type === 'DOMMouseScroll') { self.scrollDisp(ev.detail < 0 ? -1 : 1); } else { self.scrollDisp(ev.wheelDeltaY > 0 ? -1 : 1); - } + }*/ return self.cancel(ev); }); }; @@ -1604,6 +1665,8 @@ // this.maxRange(); this.updateRange(this.scrollTop); this.updateRange(this.scrollBottom); + + this.viewport.syncScrollArea(); }; /** From 06ca03ae9c02ef15f108860719cebed5d0c096a4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 2 Aug 2016 18:36:24 -0700 Subject: [PATCH 03/16] Get it working --- src/xterm.js | 96 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 23 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index 58e1ae44..2092cbe5 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -332,12 +332,15 @@ this.scrollArea.classList.add('xterm-scroll-area'); this.viewportElement.appendChild(this.scrollArea); this.currentHeight = 0; - this.lastScrollPosition; + this.lastScrollPosition = 0; + this.waitingForScroll = false; + this.lastRecordedBufferLength = this.terminal.lines.length; - this.terminal.on('refresh', this.refreshRowHeight.bind(this)); + //this.terminal.on('refresh', this.refreshRowHeight.bind(this)); // TODO: Attach this to a more sensible event this.terminal.on('refresh', this.syncScrollArea.bind(this)); - this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); + //this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); + this.viewportElement.addEventListener('scroll', this.onScroll2.bind(this)); } Viewport.prototype.refreshRowHeight = function() { @@ -354,34 +357,77 @@ Viewport.prototype.syncScrollArea = function() { //console.log('ybase: ' + this.terminal.ybase); //console.log('ydisp: ' + this.terminal.ydisp); - this.viewportElement.scrollTop = this.terminal.ydisp * this.currentHeight; + if (this.lastRecordedBufferLength !== this.terminal.lines.length) { + this.lastRecordedBufferLength = this.terminal.lines.length; + this.refreshRowHeight(); + this.viewportElement.scrollTop = this.terminal.ydisp * this.currentHeight; + } + }; + + Viewport.prototype.onScroll2 = function(ev) { + console.log('scroll, scrollTop=' + this.viewportElement.scrollTop); + var newRow = Math.round(this.viewportElement.scrollTop / this.currentHeight); + var diff = newRow - this.terminal.ydisp; + console.log('scrolling to: ' + diff); + this.terminal.scrollDisp(diff); }; Viewport.prototype.onScroll = function(ev) { - console.log('onScroll', ev); - console.log('this.viewportElement.scrollTop: ' + this.viewportElement.scrollTop); - console.log('row: ' + Math.round(this.viewportElement.scrollTop / this.currentHeight)); - console.log('lastScrollPosition: ' + this.lastScrollPosition); - console.log(' scrollTop: ' + this.viewportElement.scrollTop); - // This helps get around the case where scrollTop changes by 1 pixel by pressing up or down on the scrollbar // It gets complicated as the scrollbar sometimes locks to a multiple of the row - if (this.lastScrollPosition !== this.viewportElement.scrollTop) { - console.log('in if'); - this.lastScrollPosition = this.viewportElement.scrollTop; + //if (this.lastScrollPosition !== this.viewportElement.scrollTop) { + /*console.log('onScroll', ev); + console.log('lastScrollPosition: ' + this.lastScrollPosition); + console.log(' scrollTop: ' + this.viewportElement.scrollTop);*/ + //this.viewportElement.scrollTop = Math.round(this.viewportElement.scrollTop); + //this.lastScrollPosition = this.viewportElement.scrollTop; + console.log('scrollTop=' + this.viewportElement.scrollTop); var newRow = this.viewportElement.scrollTop / this.currentHeight; - var diff = newRow - this.terminal.ydisp; + + if (newRow % 1 > 0) { + // Only accept new scroll events once it has actually scrolled + if (!this.waitingForScroll) { + this.waitingForScroll = true; + var diff = newRow - this.terminal.ydisp; + var multiplier = diff < 0 ? -1 : 1; + diff = Math.max(1, Math.round(Math.abs(diff))) * multiplier; + console.log('newRow=' + newRow + ', diff='+diff+', scrolling to=' + ((this.terminal.ydisp + diff) * this.currentHeight)); + + //this.terminal.scrollDisp(diff); + this.viewportElement.scrollTop = (this.terminal.ydisp + diff) * this.currentHeight; + this.waitingForScrollTop = (this.terminal.ydisp + diff) * this.currentHeight; + this.terminal.scrollDisp(newRow - this.terminal.ydisp); + } + ev.preventDefault(); + ev.stopPropagation(); + ev.stopImmediatePropagation(); + return; + } + + if (newRow !== this.terminal.ydisp) { + if (this.waitingForScrollTop !== this.viewportElement.scrollTop) { + console.log('wtf? this.waitingForScrollTop='+this.waitingForScrollTop+', this.viewportElement.scrollTop='+this.viewportElement.scrollTop); + } + this.waitingForScroll = false; + this.terminal.scrollDisp(newRow - this.terminal.ydisp); + ev.preventDefault(); + ev.stopPropagation(); + return; + } + + + /*var diff = newRow - this.terminal.ydisp; + console.log('newRow=' + newRow + ', diff=' + diff); if (diff === 0) { - console.log('diff = 0'); + //console.log('diff = 0'); ev.preventDefault(); ev.stopPropagation(); return; } var multiplier = diff < 0 ? -1 : 1; diff = Math.max(1, Math.round(Math.abs(diff))) * multiplier; - console.log('onScroll diff: ' + diff); - this.terminal.scrollDisp(diff); - } + this.terminal.scrollDisp(diff);*/ + //} ev.preventDefault(); ev.stopPropagation(); }; @@ -393,11 +439,15 @@ //console.log('this.currentHeight: ' + this.currentHeight); //console.log(ev); //this.terminal.scrollDisp(this.terminal.ydisp - newRow); - var multiplier = ev.deltaY < 0 ? -1 : 1; + /*var multiplier = ev.deltaY < 0 ? -1 : 1; var diff = Math.max(1, Math.round(Math.abs(ev.deltaY / this.currentHeight))); console.log('diff * multiplier: ' + diff * multiplier); - this.terminal.scrollDisp(diff * multiplier); + this.terminal.scrollDisp(diff * multiplier);*/ + + // Defer scroll logic to the onScroll function + this.viewportElement.scrollTop += ev.deltaY; + //this.lastScrollPosition = this.viewportElement.scrollTop; }; @@ -931,7 +981,7 @@ */ this.rowContainer = document.createElement('div'); this.rowContainer.classList.add('xterm-rows'); - this.viewportElement.appendChild(this.rowContainer); + this.element.appendChild(this.rowContainer); this.children = []; /* @@ -1458,7 +1508,7 @@ if (end - start >= this.rows / 2) { parent = this.element.parentNode; if (parent) { - this.viewportElement.removeChild(this.rowContainer); + this.element.removeChild(this.rowContainer); } } @@ -1608,7 +1658,7 @@ } if (parent) { - this.viewportElement.appendChild(this.rowContainer); + this.element.appendChild(this.rowContainer); } this.emit('refresh', {element: this.element, start: start, end: end}); From aac60eb029b64536c4e23071184faf0ffcab86eb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Aug 2016 10:16:08 -0700 Subject: [PATCH 04/16] Add support for WheelEvent.deltaMode --- src/xterm.js | 120 ++++++++++++--------------------------------------- 1 file changed, 27 insertions(+), 93 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index 2092cbe5..a4106c5f 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -340,23 +340,22 @@ // TODO: Attach this to a more sensible event this.terminal.on('refresh', this.syncScrollArea.bind(this)); //this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); - this.viewportElement.addEventListener('scroll', this.onScroll2.bind(this)); + this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); } Viewport.prototype.refreshRowHeight = function() { var size = this.charMeasureElement.getBoundingClientRect(); - if (size.height > 0 && size.height !== this.currentHeight) { - this.currentHeight = size.height; - this.viewportElement.style.lineHeight = size.height + 'px'; - this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; + if (size.height > 0) { + if (size.height !== this.currentHeight) { + this.currentHeight = size.height; + this.viewportElement.style.lineHeight = size.height + 'px'; + this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; + } + this.scrollArea.style.height = (size.height * this.terminal.lines.length) + 'px'; } - // TODO: Should this be lines.length - ybase? or lines.length - rows? - this.scrollArea.style.height = (size.height * this.terminal.lines.length) + 'px'; }; Viewport.prototype.syncScrollArea = function() { - //console.log('ybase: ' + this.terminal.ybase); - //console.log('ydisp: ' + this.terminal.ydisp); if (this.lastRecordedBufferLength !== this.terminal.lines.length) { this.lastRecordedBufferLength = this.terminal.lines.length; this.refreshRowHeight(); @@ -364,91 +363,31 @@ } }; - Viewport.prototype.onScroll2 = function(ev) { - console.log('scroll, scrollTop=' + this.viewportElement.scrollTop); + /** + * Handles scroll events on the viewport, calculating the new viewport and requesting the + * terminal to scroll to it. + */ + Viewport.prototype.onScroll = function(ev) { var newRow = Math.round(this.viewportElement.scrollTop / this.currentHeight); var diff = newRow - this.terminal.ydisp; - console.log('scrolling to: ' + diff); this.terminal.scrollDisp(diff); }; - Viewport.prototype.onScroll = function(ev) { - // This helps get around the case where scrollTop changes by 1 pixel by pressing up or down on the scrollbar - // It gets complicated as the scrollbar sometimes locks to a multiple of the row - //if (this.lastScrollPosition !== this.viewportElement.scrollTop) { - /*console.log('onScroll', ev); - console.log('lastScrollPosition: ' + this.lastScrollPosition); - console.log(' scrollTop: ' + this.viewportElement.scrollTop);*/ - //this.viewportElement.scrollTop = Math.round(this.viewportElement.scrollTop); - //this.lastScrollPosition = this.viewportElement.scrollTop; - console.log('scrollTop=' + this.viewportElement.scrollTop); - var newRow = this.viewportElement.scrollTop / this.currentHeight; - - if (newRow % 1 > 0) { - // Only accept new scroll events once it has actually scrolled - if (!this.waitingForScroll) { - this.waitingForScroll = true; - var diff = newRow - this.terminal.ydisp; - var multiplier = diff < 0 ? -1 : 1; - diff = Math.max(1, Math.round(Math.abs(diff))) * multiplier; - console.log('newRow=' + newRow + ', diff='+diff+', scrolling to=' + ((this.terminal.ydisp + diff) * this.currentHeight)); - - //this.terminal.scrollDisp(diff); - this.viewportElement.scrollTop = (this.terminal.ydisp + diff) * this.currentHeight; - this.waitingForScrollTop = (this.terminal.ydisp + diff) * this.currentHeight; - this.terminal.scrollDisp(newRow - this.terminal.ydisp); - } - ev.preventDefault(); - ev.stopPropagation(); - ev.stopImmediatePropagation(); - return; - } - - if (newRow !== this.terminal.ydisp) { - if (this.waitingForScrollTop !== this.viewportElement.scrollTop) { - console.log('wtf? this.waitingForScrollTop='+this.waitingForScrollTop+', this.viewportElement.scrollTop='+this.viewportElement.scrollTop); - } - this.waitingForScroll = false; - this.terminal.scrollDisp(newRow - this.terminal.ydisp); - ev.preventDefault(); - ev.stopPropagation(); - return; - } - - - /*var diff = newRow - this.terminal.ydisp; - console.log('newRow=' + newRow + ', diff=' + diff); - if (diff === 0) { - //console.log('diff = 0'); - ev.preventDefault(); - ev.stopPropagation(); - return; - } - var multiplier = diff < 0 ? -1 : 1; - diff = Math.max(1, Math.round(Math.abs(diff))) * multiplier; - this.terminal.scrollDisp(diff);*/ - //} - ev.preventDefault(); - ev.stopPropagation(); - }; - + /** + * Handles mouse wheel events by adjusting the viewport's scrollTop and delegating the actual + * scrolling to `onScroll`, this event needs to be attached manually by the consumer of + * `Viewport`. + * @param {WheelEvent} ev The mouse wheel event. + */ Viewport.prototype.onWheel = function(ev) { - //var newRow = this.viewportElement.scrollTop / this.currentHeight; - //console.log('ydisp: ' + this.terminal.ydisp); - //console.log('this.viewportElement.scrollTop: ' + this.viewportElement.scrollTop); - //console.log('this.currentHeight: ' + this.currentHeight); - //console.log(ev); - //this.terminal.scrollDisp(this.terminal.ydisp - newRow); - /*var multiplier = ev.deltaY < 0 ? -1 : 1; - var diff = Math.max(1, Math.round(Math.abs(ev.deltaY / this.currentHeight))); - - console.log('diff * multiplier: ' + diff * multiplier); - this.terminal.scrollDisp(diff * multiplier);*/ - - // Defer scroll logic to the onScroll function - this.viewportElement.scrollTop += ev.deltaY; - - //this.lastScrollPosition = this.viewportElement.scrollTop; + // Fallback to WheelEvent.DOM_DELTA_PIXEL + var multiplier = 1; + if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { + multiplier = this.currentHeight; + } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + multiplier = this.currentHeight * this.terminal.rows; + } + this.viewportElement.scrollTop += ev.deltaY * multiplier; }; /** @@ -1405,11 +1344,6 @@ if (self.mouseEvents) return; if (self.applicationKeypad) return; self.viewport.onWheel(ev); - /*if (ev.type === 'DOMMouseScroll') { - self.scrollDisp(ev.detail < 0 ? -1 : 1); - } else { - self.scrollDisp(ev.wheelDeltaY > 0 ? -1 : 1); - }*/ return self.cancel(ev); }); }; From 363c647a5f9f3c6db350aceb08a0e9a14f8eb353 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Aug 2016 11:26:52 -0700 Subject: [PATCH 05/16] Sync scroll bar on more sensible events --- src/xterm.js | 52 +++++++++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index a4106c5f..d3e9a403 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -325,31 +325,36 @@ } function Viewport(terminal, viewportElement, charMeasureElement) { + // TODO: Remove cyclic dependency on Terminal this.terminal = terminal; this.viewportElement = viewportElement; this.charMeasureElement = charMeasureElement; this.scrollArea = document.createElement('div'); this.scrollArea.classList.add('xterm-scroll-area'); this.viewportElement.appendChild(this.scrollArea); - this.currentHeight = 0; + this.currentRowHeight = 0; this.lastScrollPosition = 0; this.waitingForScroll = false; - this.lastRecordedBufferLength = this.terminal.lines.length; + this.lastRecordedBufferLength = 0; + this.lastRecordedViewportHeight = 0; - //this.terminal.on('refresh', this.refreshRowHeight.bind(this)); - // TODO: Attach this to a more sensible event - this.terminal.on('refresh', this.syncScrollArea.bind(this)); - //this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); + this.terminal.on('scroll', this.syncScrollArea.bind(this)); + this.terminal.on('resize', this.syncScrollArea.bind(this)); this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); + + this.syncScrollArea(); } Viewport.prototype.refreshRowHeight = function() { var size = this.charMeasureElement.getBoundingClientRect(); if (size.height > 0) { - if (size.height !== this.currentHeight) { - this.currentHeight = size.height; + if (size.height !== this.currentRowHeight) { + this.currentRowHeight = size.height; this.viewportElement.style.lineHeight = size.height + 'px'; - this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; + } + if (this.lastRecordedViewportHeight !== this.terminal.rows) { + this.lastRecordedViewportHeight = this.terminal.rows; + this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; } this.scrollArea.style.height = (size.height * this.terminal.lines.length) + 'px'; } @@ -359,7 +364,10 @@ if (this.lastRecordedBufferLength !== this.terminal.lines.length) { this.lastRecordedBufferLength = this.terminal.lines.length; this.refreshRowHeight(); - this.viewportElement.scrollTop = this.terminal.ydisp * this.currentHeight; + } + var scrollTop = this.terminal.ydisp * this.currentRowHeight; + if (this.viewportElement.scrollTop !== scrollTop) { + this.viewportElement.scrollTop = scrollTop; } }; @@ -368,9 +376,9 @@ * terminal to scroll to it. */ Viewport.prototype.onScroll = function(ev) { - var newRow = Math.round(this.viewportElement.scrollTop / this.currentHeight); + var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); var diff = newRow - this.terminal.ydisp; - this.terminal.scrollDisp(diff); + this.terminal.scrollDisp(diff, true); }; /** @@ -383,9 +391,9 @@ // Fallback to WheelEvent.DOM_DELTA_PIXEL var multiplier = 1; if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { - multiplier = this.currentHeight; + multiplier = this.currentRowHeight; } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { - multiplier = this.currentHeight * this.terminal.rows; + multiplier = this.currentRowHeight * this.terminal.rows; } this.viewportElement.scrollTop += ev.deltaY * multiplier; }; @@ -955,14 +963,12 @@ this.charMeasureElement.innerHTML = 'W'; this.helperContainer.appendChild(this.charMeasureElement); - - this.viewport = new Viewport(this, this.viewportElement, this.charMeasureElement); - for (; i < this.rows; i++) { this.insertRow(); } this.parent.appendChild(this.element); + this.viewport = new Viewport(this, this.viewportElement, this.charMeasureElement); // Draw the screen. this.refresh(0, this.rows - 1); @@ -1650,14 +1656,17 @@ this.updateRange(this.scrollTop); this.updateRange(this.scrollBottom); - this.viewport.syncScrollArea(); + this.emit('scroll', this.ydisp); }; /** * Scroll the display of the terminal * @param {number} disp The number of lines to scroll down (negatives scroll up). + * @param {boolean} suppressScrollEvent Don't emit the scroll event as scrollDisp. This is used + * to avoid unwanted events being handled by the veiwport when the event was triggered from the + * viewport originally. */ - Terminal.prototype.scrollDisp = function(disp) { + Terminal.prototype.scrollDisp = function(disp, suppressScrollEvent) { this.ydisp += disp; if (this.ydisp > this.ybase) { @@ -1666,6 +1675,10 @@ this.ydisp = 0; } + if (!suppressScrollEvent) { + this.emit('scroll', this.ydisp); + } + this.refresh(0, this.rows - 1); }; @@ -1681,6 +1694,7 @@ if (this.ybase !== this.ydisp) { this.ydisp = this.ybase; + this.emit('scroll', this.ydisp); this.maxRange(); } From e7e143429351f1d1d63aa72cae1779acc593dacf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Aug 2016 12:47:36 -0700 Subject: [PATCH 06/16] Clean up, refresh sizes when font-size changes --- src/xterm.js | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index d3e9a403..27918aa5 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -324,8 +324,11 @@ this.textarea.style.top = ''; } + /** + * Represents the viewport of a terminal, the visible area within the larger buffer of output. + * Logic for the virtual scroll bar is included in this object. + */ function Viewport(terminal, viewportElement, charMeasureElement) { - // TODO: Remove cyclic dependency on Terminal this.terminal = terminal; this.viewportElement = viewportElement; this.charMeasureElement = charMeasureElement; @@ -333,8 +336,6 @@ this.scrollArea.classList.add('xterm-scroll-area'); this.viewportElement.appendChild(this.scrollArea); this.currentRowHeight = 0; - this.lastScrollPosition = 0; - this.waitingForScroll = false; this.lastRecordedBufferLength = 0; this.lastRecordedViewportHeight = 0; @@ -345,8 +346,14 @@ this.syncScrollArea(); } - Viewport.prototype.refreshRowHeight = function() { - var size = this.charMeasureElement.getBoundingClientRect(); + /** + * Refreshes row height, setting line-height, viewport height and scroll area height if + * necessary. + * @param {number|undefined} charSize A character size measurement bounding rect object, if it + * doesn't exist it will be created. + */ + Viewport.prototype.refresh = function(charSize) { + var size = charSize || this.charMeasureElement.getBoundingClientRect(); if (size.height > 0) { if (size.height !== this.currentRowHeight) { this.currentRowHeight = size.height; @@ -360,10 +367,19 @@ } }; + /** + * Updates dimensions and synchronizes the scroll area if necessary. + */ Viewport.prototype.syncScrollArea = function() { if (this.lastRecordedBufferLength !== this.terminal.lines.length) { this.lastRecordedBufferLength = this.terminal.lines.length; - this.refreshRowHeight(); + this.refresh(); + } else { + // If size has changed, refresh viewport + var size = this.charMeasureElement.getBoundingClientRect(); + if (size.height !== this.currentRowHeight) { + this.refresh(size); + } } var scrollTop = this.terminal.ydisp * this.currentRowHeight; if (this.viewportElement.scrollTop !== scrollTop) { @@ -374,6 +390,7 @@ /** * Handles scroll events on the viewport, calculating the new viewport and requesting the * terminal to scroll to it. + * @param {Event} ev The scroll event. */ Viewport.prototype.onScroll = function(ev) { var newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); From fe905bace58d7ef11833a716c704684ccfdfbebe Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Aug 2016 12:49:12 -0700 Subject: [PATCH 07/16] Prevent the page from scrolling when the terminal scrolls --- src/xterm.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/xterm.js b/src/xterm.js index 27918aa5..ffefbb39 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -413,6 +413,8 @@ multiplier = this.currentRowHeight * this.terminal.rows; } this.viewportElement.scrollTop += ev.deltaY * multiplier; + // Prevent the page from scrolling when the terminal scrolls + ev.preventDefault(); }; /** From 38fa2d78da5bb4da846b8ceb2c8c4643b744d690 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Aug 2016 13:00:07 -0700 Subject: [PATCH 08/16] Support DOMMouseScroll event (Firefox) --- src/xterm.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index ffefbb39..59746ce7 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -407,12 +407,23 @@ Viewport.prototype.onWheel = function(ev) { // Fallback to WheelEvent.DOM_DELTA_PIXEL var multiplier = 1; - if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { + var delta = 0; + if (ev.type === 'DOMMouseScroll') { + if (ev.axis !== MouseScrollEvent.VERTICAL_AXIS) { + return; + } + delta = ev.detail; + // Firefox treats ev.detail as lines, not pixels multiplier = this.currentRowHeight; - } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { - multiplier = this.currentRowHeight * this.terminal.rows; + } else if (ev.type === 'mousewheel') { + delta = ev.deltaY + if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { + multiplier = this.currentRowHeight; + } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + multiplier = this.currentRowHeight * this.terminal.rows; + } } - this.viewportElement.scrollTop += ev.deltaY * multiplier; + this.viewportElement.scrollTop += delta * multiplier; // Prevent the page from scrolling when the terminal scrolls ev.preventDefault(); }; From 6f70984ab6867327fb38e69f7e83fbadaca057af Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Aug 2016 13:41:54 -0700 Subject: [PATCH 09/16] Drop support for old mouse wheel APIs All browsers have supported the WheelEvent (onwheel) for sometime now, since Firefox does not support onmousewheel which is also non-standard but works with the standard interface, it makes sense to drop support now. --- src/xterm.js | 42 ++++++++++++++++-------------------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index 59746ce7..45e0ba59 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -405,25 +405,18 @@ * @param {WheelEvent} ev The mouse wheel event. */ Viewport.prototype.onWheel = function(ev) { + if (ev.deltaY === 0) { + // Do nothing if it's not a vertical scroll event + return; + } // Fallback to WheelEvent.DOM_DELTA_PIXEL var multiplier = 1; - var delta = 0; - if (ev.type === 'DOMMouseScroll') { - if (ev.axis !== MouseScrollEvent.VERTICAL_AXIS) { - return; - } - delta = ev.detail; - // Firefox treats ev.detail as lines, not pixels + if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { multiplier = this.currentRowHeight; - } else if (ev.type === 'mousewheel') { - delta = ev.deltaY - if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { - multiplier = this.currentRowHeight; - } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { - multiplier = this.currentRowHeight * this.terminal.rows; - } + } else if (ev.deltaMode === WheelEvent.DOM_DELTA_PAGE) { + multiplier = this.currentRowHeight * this.terminal.rows; } - this.viewportElement.scrollTop += delta * multiplier; + this.viewportElement.scrollTop += ev.deltaY * multiplier; // Prevent the page from scrolling when the terminal scrolls ev.preventDefault(); }; @@ -1064,11 +1057,10 @@ */ Terminal.prototype.bindMouse = function() { var el = this.element, self = this, pressed = 32; - var wheelEvent = ('onmousewheel' in this.context) ? 'mousewheel' : 'DOMMouseScroll'; - // mouseup, mousedown, mousewheel + // mouseup, mousedown, wheel // left click: ^[[M 3<^[[M#3< - // mousewheel up: ^[[M`3> + // wheel up: ^[[M`3> function sendButton(ev) { var button , pos; @@ -1091,7 +1083,7 @@ // button, just in case. pressed = 32; break; - case wheelEvent: + case 'wheel': // nothing. don't // interfere with // `pressed`. @@ -1252,7 +1244,7 @@ ? 64 : 65; break; - case 'mousewheel': + case 'wheel': button = ev.wheelDeltaY > 0 ? 64 : 65; @@ -1322,9 +1314,7 @@ return { x: x, y: y, - type: (ev.overrideType || ev.type) === wheelEvent - ? 'mousewheel' - : (ev.overrideType || ev.type) + type: 'wheel' }; } @@ -1365,7 +1355,7 @@ // on(self.document, 'mousemove', sendMove); //} - on(el, wheelEvent, function(ev) { + on(el, 'wheel', function(ev) { if (!self.mouseEvents) return; if (self.x10Mouse || self.vt300Mouse @@ -1374,9 +1364,9 @@ return self.cancel(ev); }); - // allow mousewheel scrolling in + // allow wheel scrolling in // the shell for example - on(el, wheelEvent, function(ev) { + on(el, 'wheel', function(ev) { if (self.mouseEvents) return; if (self.applicationKeypad) return; self.viewport.onWheel(ev); From 1b886a4420f72d2ea9e22cba0043e769e2c205c7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Aug 2016 18:12:44 -0700 Subject: [PATCH 10/16] Disable the scroll bar when in application keypad mode --- src/xterm.js | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/xterm.js b/src/xterm.js index 45e0ba59..0569e9a5 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -326,7 +326,7 @@ /** * Represents the viewport of a terminal, the visible area within the larger buffer of output. - * Logic for the virtual scroll bar is included in this object. + * Logic for the virtual scroll bar is included in this object. */ function Viewport(terminal, viewportElement, charMeasureElement) { this.terminal = terminal; @@ -348,7 +348,7 @@ /** * Refreshes row height, setting line-height, viewport height and scroll area height if - * necessary. + * necessary. * @param {number|undefined} charSize A character size measurement bounding rect object, if it * doesn't exist it will be created. */ @@ -361,7 +361,7 @@ } if (this.lastRecordedViewportHeight !== this.terminal.rows) { this.lastRecordedViewportHeight = this.terminal.rows; - this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; + this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; } this.scrollArea.style.height = (size.height * this.terminal.lines.length) + 'px'; } @@ -371,6 +371,12 @@ * Updates dimensions and synchronizes the scroll area if necessary. */ Viewport.prototype.syncScrollArea = function() { + if (this.isApplicationMode) { + this.lastRecordedBufferLength = this.currentRowHeight * this.terminal.rows; + this.refresh(); + return; + } + if (this.lastRecordedBufferLength !== this.terminal.lines.length) { this.lastRecordedBufferLength = this.terminal.lines.length; this.refresh(); @@ -387,6 +393,16 @@ } }; + /** + * Sets the application mode of the viewport. + * @param {boolean} isApplicationMode Sets whether the terminal is in application mode. true + * for application mode (DECKPAM) and false for normal mode (DECKPNM). + */ + Viewport.prototype.setApplicationMode = function(isApplicationMode) { + this.isApplicationMode = isApplicationMode; + this.syncScrollArea(); + }; + /** * Handles scroll events on the viewport, calculating the new viewport and requesting the * terminal to scroll to it. @@ -409,7 +425,7 @@ // Do nothing if it's not a vertical scroll event return; } - // Fallback to WheelEvent.DOM_DELTA_PIXEL + // Fallback to WheelEvent.DOM_DELTA_PIXEL var multiplier = 1; if (ev.deltaMode === WheelEvent.DOM_DELTA_LINE) { multiplier = this.currentRowHeight; @@ -2041,17 +2057,19 @@ this.tabSet(); break; - // ESC = Application Keypad (DECPAM). + // ESC = Application Keypad (DECKPAM). case '=': this.log('Serial port requested application keypad.'); this.applicationKeypad = true; + this.viewport.setApplicationMode(true); this.state = normal; break; - // ESC > Normal Keypad (DECPNM). + // ESC > Normal Keypad (DECKPNM). case '>': this.log('Switching back to normal keypad.'); this.applicationKeypad = false; + this.viewport.setApplicationMode(false); this.state = normal; break; @@ -4295,6 +4313,7 @@ case 66: this.log('Serial port requested application keypad.'); this.applicationKeypad = true; + this.viewport.setApplicationMode(true); break; case 9: // X10 Mouse // no release, no motion, no wheel, no modifiers. @@ -4493,6 +4512,7 @@ break; case 66: this.log('Switching back to normal keypad.'); + this.viewport.setApplicationMode(false); this.applicationKeypad = false; break; case 9: // X10 Mouse From 61201525a8e7e253d71145f670a5a1f1599bb6d1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Aug 2016 18:27:19 -0700 Subject: [PATCH 11/16] Remaining jsdoc --- src/xterm.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/xterm.js b/src/xterm.js index 0569e9a5..0d8a33ed 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -327,6 +327,10 @@ /** * Represents the viewport of a terminal, the visible area within the larger buffer of output. * Logic for the virtual scroll bar is included in this object. + * @param {Terminal} terminal The Terminal object. + * @param {HTMLElement} viewportElement The DOM element acting as the viewport + * @param {HTMLElement} charMeasureElement A DOM element used to measure the character size of + * the terminal. */ function Viewport(terminal, viewportElement, charMeasureElement) { this.terminal = terminal; From 0a34885fbcde3411ffadcbb64315a1522ee8e3bc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Aug 2016 18:27:43 -0700 Subject: [PATCH 12/16] Fix line-height after rowContainer moved out of viewport --- src/xterm.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/xterm.js b/src/xterm.js index 0d8a33ed..573863f8 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -362,6 +362,7 @@ if (size.height !== this.currentRowHeight) { this.currentRowHeight = size.height; this.viewportElement.style.lineHeight = size.height + 'px'; + this.terminal.rowContainer.style.lineHeight = size.height + 'px'; } if (this.lastRecordedViewportHeight !== this.terminal.rows) { this.lastRecordedViewportHeight = this.terminal.rows; From 7048f6edf7e973e86a8836bf1fe50d6943d6f6cf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Aug 2016 18:34:04 -0700 Subject: [PATCH 13/16] Add some tests, fix app keypad mode bug --- src/xterm.js | 24 +++++----- test/composition-helper-test.js | 6 +-- test/viewport-test.js | 79 +++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 14 deletions(-) create mode 100644 test/viewport-test.js diff --git a/src/xterm.js b/src/xterm.js index 573863f8..e26288c5 100644 --- a/src/xterm.js +++ b/src/xterm.js @@ -219,7 +219,7 @@ } return true; - } + }; /** * Finalizes the composition, resuming regular input actions. This is called when a composition @@ -275,7 +275,7 @@ } }, 0); } - } + }; /** * Apply any changes made to the textarea after the current event chain is allowed to complete. @@ -296,7 +296,7 @@ } } }, 0); - } + }; /** * Positions the composition view on top of the cursor and the textarea just below it (so the @@ -322,7 +322,7 @@ CompositionHelper.prototype.clearTextareaPosition = function() { this.textarea.style.left = ''; this.textarea.style.top = ''; - } + }; /** * Represents the viewport of a terminal, the visible area within the larger buffer of output. @@ -332,13 +332,11 @@ * @param {HTMLElement} charMeasureElement A DOM element used to measure the character size of * the terminal. */ - function Viewport(terminal, viewportElement, charMeasureElement) { + function Viewport(terminal, viewportElement, scrollArea, charMeasureElement) { this.terminal = terminal; this.viewportElement = viewportElement; + this.scrollArea = scrollArea; this.charMeasureElement = charMeasureElement; - this.scrollArea = document.createElement('div'); - this.scrollArea.classList.add('xterm-scroll-area'); - this.viewportElement.appendChild(this.scrollArea); this.currentRowHeight = 0; this.lastRecordedBufferLength = 0; this.lastRecordedViewportHeight = 0; @@ -368,7 +366,7 @@ this.lastRecordedViewportHeight = this.terminal.rows; this.viewportElement.style.height = size.height * this.terminal.rows + 'px'; } - this.scrollArea.style.height = (size.height * this.terminal.lines.length) + 'px'; + this.scrollArea.style.height = (size.height * this.lastRecordedBufferLength) + 'px'; } }; @@ -377,7 +375,7 @@ */ Viewport.prototype.syncScrollArea = function() { if (this.isApplicationMode) { - this.lastRecordedBufferLength = this.currentRowHeight * this.terminal.rows; + this.lastRecordedBufferLength = this.terminal.rows; this.refresh(); return; } @@ -965,6 +963,9 @@ this.viewportElement = document.createElement('div'); this.viewportElement.classList.add('xterm-viewport'); this.element.appendChild(this.viewportElement); + this.viewportScrollArea = document.createElement('div'); + this.viewportScrollArea.classList.add('xterm-scroll-area'); + this.viewportElement.appendChild(this.viewportScrollArea); /* * Create the container that will hold the lines of the terminal and then @@ -1012,7 +1013,7 @@ } this.parent.appendChild(this.element); - this.viewport = new Viewport(this, this.viewportElement, this.charMeasureElement); + this.viewport = new Viewport(this, this.viewportElement, this.viewportScrollArea, this.charMeasureElement); // Draw the screen. this.refresh(0, this.rows - 1); @@ -5387,6 +5388,7 @@ Terminal.EventEmitter = EventEmitter; Terminal.CompositionHelper = CompositionHelper; + Terminal.Viewport = Viewport; Terminal.inherits = inherits; /** diff --git a/test/composition-helper-test.js b/test/composition-helper-test.js index 7bc4ee48..ceb2b4ea 100644 --- a/test/composition-helper-test.js +++ b/test/composition-helper-test.js @@ -19,14 +19,14 @@ describe('CompositionHelper', function () { top: 0 }, textContent: '' - } + }; textarea = { value: '', style: { left: 0, top: 0 } - } + }; terminal = { element: { querySelector: function () { @@ -36,7 +36,7 @@ describe('CompositionHelper', function () { handler: function (text) { handledText += text; } - } + }; handledText = ''; compositionHelper = new Terminal.CompositionHelper(textarea, compositionView, terminal); }); diff --git a/test/viewport-test.js b/test/viewport-test.js new file mode 100644 index 00000000..39bcedf9 --- /dev/null +++ b/test/viewport-test.js @@ -0,0 +1,79 @@ +var assert = require('chai').assert; +var Terminal = require('../src/xterm'); + +describe('Viewport', function () { + var terminal; + var viewportElement; + var charMeasureElement; + var viewport; + + var CHARACTER_HEIGHT = 10; + + beforeEach(function () { + terminal = { + lines: [], + rows: 0, + ydisp: 0, + on: function () {}, + rowContainer: { + style: { + lineHeight: 0 + } + } + }; + viewportElement = { + addEventListener: function () {}, + style: { + height: 0, + lineHeight: 0 + } + }; + scrollAreaElement = { + style: { + height: 0 + } + }; + charMeasureElement = { + getBoundingClientRect: function () { + return { width: null, height: CHARACTER_HEIGHT }; + } + }; + viewport = new Terminal.Viewport(terminal, viewportElement, scrollAreaElement, charMeasureElement); + }); + + describe('Public API', function () { + it('should define Viewport.prototype.onWheel', function () { + assert.isDefined(Terminal.Viewport.prototype.onWheel); + }); + it('should define Viewport.prototype.setApplicationMode', function () { + assert.isDefined(Terminal.Viewport.prototype.setApplicationMode); + }); + }); + + describe('setApplicationMode', function () { + it('should restrict the scroll area to the viewport', function () { + terminal.lines.push(''); + terminal.lines.push(''); + terminal.rows = 1; + viewport.syncScrollArea(); + assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px'); + viewport.setApplicationMode(true); + assert.equal(scrollAreaElement.style.height, CHARACTER_HEIGHT + 'px'); + viewport.setApplicationMode(false); + assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px'); + }); + }); + + describe('refresh', function () { + it('should set the line-height of the terminal', function () { + assert.equal(viewportElement.style.lineHeight, CHARACTER_HEIGHT + 'px'); + assert.equal(terminal.rowContainer.style.lineHeight, CHARACTER_HEIGHT + 'px'); + charMeasureElement.getBoundingClientRect = function () { + return { width: null, height: 1 }; + }; + viewport.refresh(); + assert.equal(viewportElement.style.lineHeight, '1px'); + assert.equal(terminal.rowContainer.style.lineHeight, '1px'); + }); + }); +}); From 2c9c95f53169e9a4745164ef38eaa5237d14ba76 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 4 Aug 2016 18:56:33 -0700 Subject: [PATCH 14/16] Add another test --- test/viewport-test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/viewport-test.js b/test/viewport-test.js index 39bcedf9..186ef982 100644 --- a/test/viewport-test.js +++ b/test/viewport-test.js @@ -76,4 +76,19 @@ describe('Viewport', function () { assert.equal(terminal.rowContainer.style.lineHeight, '1px'); }); }); + + describe('syncScrollArea', function () { + it('should sync the scroll area', function () { + terminal.lines.push(''); + terminal.rows = 1; + assert.equal(scrollAreaElement.style.height, 0 * CHARACTER_HEIGHT + 'px'); + viewport.syncScrollArea(); + assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px'); + assert.equal(scrollAreaElement.style.height, 1 * CHARACTER_HEIGHT + 'px'); + terminal.lines.push(''); + viewport.syncScrollArea(); + assert.equal(viewportElement.style.height, 1 * CHARACTER_HEIGHT + 'px'); + assert.equal(scrollAreaElement.style.height, 2 * CHARACTER_HEIGHT + 'px'); + }); + }); }); From a557a82b40d375caae8cee2d40d40aed150962cc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 8 Aug 2016 10:22:19 -0700 Subject: [PATCH 15/16] Fix scrollbar being transparent on OS X --- src/xterm.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/xterm.css b/src/xterm.css index f18ef3d7..8f2e43ce 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -104,6 +104,8 @@ } .terminal .xterm-viewport { + /* On OS X this is required in order for the scroll bar to appear fully opaque */ + background-color: #000; overflow-y: scroll; } From e60513f5540a035547ba9fa15efce3830bdb2caa Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 8 Aug 2016 10:22:34 -0700 Subject: [PATCH 16/16] Fix outline showing up when clicking terminal --- src/xterm.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xterm.css b/src/xterm.css index 8f2e43ce..e46f02d0 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -43,7 +43,8 @@ position: relative; } -.terminal.focus { +.terminal.focus, +.terminal:focus { outline: none; }