mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into control-seq-handler
This commit is contained in:
@@ -12,7 +12,6 @@ npm-debug.log
|
||||
/.idea/
|
||||
.env
|
||||
build/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
fixtures/typings-test/*.js
|
||||
package-lock.json
|
||||
|
||||
Vendored
+44
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Unit Tests",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha.cmd"
|
||||
},
|
||||
"runtimeArgs": [
|
||||
"--colors",
|
||||
"--recursive",
|
||||
"${workspaceRoot}/lib"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"outFiles": [ "${workspaceRoot}/lib/**/*.js" ],
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Demo Client",
|
||||
"url": "http://0.0.0.0:3000",
|
||||
"windows": {
|
||||
"url": "http://127.0.0.1:3000"
|
||||
},
|
||||
"webRoot": "${workspaceFolder}/"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Demo Server",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"start-debug"
|
||||
],
|
||||
"port": 9229
|
||||
}
|
||||
]
|
||||
}
|
||||
+3
-3
@@ -113,9 +113,9 @@ function createTerminal(): void {
|
||||
});
|
||||
|
||||
addDomListener(actionElements.findPrevious, 'keyup', (e) => {
|
||||
const searchOptions = getSearchOptions();
|
||||
searchOptions.incremental = e.key !== `Enter`;
|
||||
term.findPrevious(actionElements.findPrevious.value, searchOptions);
|
||||
if (e.key === `Enter`) {
|
||||
term.findPrevious(actionElements.findPrevious.value, getSearchOptions());
|
||||
}
|
||||
});
|
||||
|
||||
// fit is called within a setTimeout, cols and rows need this.
|
||||
|
||||
+90
-84
@@ -1,100 +1,106 @@
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
var expressWs = require('express-ws')(app);
|
||||
var expressWs = require('express-ws');
|
||||
var os = require('os');
|
||||
var pty = require('node-pty');
|
||||
|
||||
var terminals = {},
|
||||
logs = {};
|
||||
function startServer() {
|
||||
var app = express();
|
||||
expressWs(app);
|
||||
|
||||
app.use('/build', express.static(__dirname + '/../build'));
|
||||
var terminals = {},
|
||||
logs = {};
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.sendFile(__dirname + '/index.html');
|
||||
});
|
||||
app.use('/build', express.static(__dirname + '/../build'));
|
||||
|
||||
app.get('/style.css', function(req, res){
|
||||
res.sendFile(__dirname + '/style.css');
|
||||
});
|
||||
|
||||
app.get('/dist/client-bundle.js', function(req, res){
|
||||
res.sendFile(__dirname + '/dist/client-bundle.js');
|
||||
});
|
||||
|
||||
app.post('/terminals', function (req, res) {
|
||||
var cols = parseInt(req.query.cols),
|
||||
rows = parseInt(req.query.rows),
|
||||
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
|
||||
name: 'xterm-color',
|
||||
cols: cols || 80,
|
||||
rows: rows || 24,
|
||||
cwd: process.env.PWD,
|
||||
env: process.env
|
||||
});
|
||||
|
||||
console.log('Created terminal with PID: ' + term.pid);
|
||||
terminals[term.pid] = term;
|
||||
logs[term.pid] = '';
|
||||
term.on('data', function(data) {
|
||||
logs[term.pid] += data;
|
||||
app.get('/', function(req, res){
|
||||
res.sendFile(__dirname + '/index.html');
|
||||
});
|
||||
res.send(term.pid.toString());
|
||||
res.end();
|
||||
});
|
||||
|
||||
app.post('/terminals/:pid/size', function (req, res) {
|
||||
var pid = parseInt(req.params.pid),
|
||||
cols = parseInt(req.query.cols),
|
||||
rows = parseInt(req.query.rows),
|
||||
term = terminals[pid];
|
||||
app.get('/style.css', function(req, res){
|
||||
res.sendFile(__dirname + '/style.css');
|
||||
});
|
||||
|
||||
term.resize(cols, rows);
|
||||
console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');
|
||||
res.end();
|
||||
});
|
||||
app.get('/dist/client-bundle.js', function(req, res){
|
||||
res.sendFile(__dirname + '/dist/client-bundle.js');
|
||||
});
|
||||
|
||||
app.ws('/terminals/:pid', function (ws, req) {
|
||||
var term = terminals[parseInt(req.params.pid)];
|
||||
console.log('Connected to terminal ' + term.pid);
|
||||
ws.send(logs[term.pid]);
|
||||
app.post('/terminals', function (req, res) {
|
||||
var cols = parseInt(req.query.cols),
|
||||
rows = parseInt(req.query.rows),
|
||||
term = pty.spawn(process.platform === 'win32' ? 'cmd.exe' : 'bash', [], {
|
||||
name: 'xterm-color',
|
||||
cols: cols || 80,
|
||||
rows: rows || 24,
|
||||
cwd: process.env.PWD,
|
||||
env: process.env
|
||||
});
|
||||
|
||||
function buffer(socket, timeout) {
|
||||
let s = '';
|
||||
let sender = null;
|
||||
return (data) => {
|
||||
s += data;
|
||||
if (!sender) {
|
||||
sender = setTimeout(() => {
|
||||
socket.send(s);
|
||||
s = '';
|
||||
sender = null;
|
||||
}, timeout);
|
||||
}
|
||||
};
|
||||
}
|
||||
const send = buffer(ws, 5);
|
||||
console.log('Created terminal with PID: ' + term.pid);
|
||||
terminals[term.pid] = term;
|
||||
logs[term.pid] = '';
|
||||
term.on('data', function(data) {
|
||||
logs[term.pid] += data;
|
||||
});
|
||||
res.send(term.pid.toString());
|
||||
res.end();
|
||||
});
|
||||
|
||||
term.on('data', function(data) {
|
||||
try {
|
||||
send(data);
|
||||
} catch (ex) {
|
||||
// The WebSocket is not open, ignore
|
||||
app.post('/terminals/:pid/size', function (req, res) {
|
||||
var pid = parseInt(req.params.pid),
|
||||
cols = parseInt(req.query.cols),
|
||||
rows = parseInt(req.query.rows),
|
||||
term = terminals[pid];
|
||||
|
||||
term.resize(cols, rows);
|
||||
console.log('Resized terminal ' + pid + ' to ' + cols + ' cols and ' + rows + ' rows.');
|
||||
res.end();
|
||||
});
|
||||
|
||||
app.ws('/terminals/:pid', function (ws, req) {
|
||||
var term = terminals[parseInt(req.params.pid)];
|
||||
console.log('Connected to terminal ' + term.pid);
|
||||
ws.send(logs[term.pid]);
|
||||
|
||||
function buffer(socket, timeout) {
|
||||
let s = '';
|
||||
let sender = null;
|
||||
return (data) => {
|
||||
s += data;
|
||||
if (!sender) {
|
||||
sender = setTimeout(() => {
|
||||
socket.send(s);
|
||||
s = '';
|
||||
sender = null;
|
||||
}, timeout);
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
term.write(msg);
|
||||
});
|
||||
ws.on('close', function () {
|
||||
term.kill();
|
||||
console.log('Closed terminal ' + term.pid);
|
||||
// Clean things up
|
||||
delete terminals[term.pid];
|
||||
delete logs[term.pid];
|
||||
});
|
||||
});
|
||||
const send = buffer(ws, 5);
|
||||
|
||||
var port = process.env.PORT || 3000,
|
||||
host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0';
|
||||
term.on('data', function(data) {
|
||||
try {
|
||||
send(data);
|
||||
} catch (ex) {
|
||||
// The WebSocket is not open, ignore
|
||||
}
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
term.write(msg);
|
||||
});
|
||||
ws.on('close', function () {
|
||||
term.kill();
|
||||
console.log('Closed terminal ' + term.pid);
|
||||
// Clean things up
|
||||
delete terminals[term.pid];
|
||||
delete logs[term.pid];
|
||||
});
|
||||
});
|
||||
|
||||
console.log('App listening to http://' + host + ':' + port);
|
||||
app.listen(port, host);
|
||||
var port = process.env.PORT || 3000,
|
||||
host = os.platform() === 'win32' ? '127.0.0.1' : '0.0.0.0';
|
||||
|
||||
console.log('App listening to http://' + host + ':' + port);
|
||||
app.listen(port, host);
|
||||
}
|
||||
|
||||
module.exports = startServer;
|
||||
|
||||
+7
-2
@@ -8,9 +8,9 @@
|
||||
const cp = require('child_process');
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const startServer = require('./server.js');
|
||||
|
||||
// Launch server
|
||||
cp.spawn('node', [path.resolve(__dirname, 'server.js')], { stdio: 'inherit' });
|
||||
startServer();
|
||||
|
||||
// Build/watch client source
|
||||
const clientConfig = {
|
||||
@@ -22,6 +22,11 @@ const clientConfig = {
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
use: ["source-map-loader"],
|
||||
enforce: "pre"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+2
-1
@@ -34,7 +34,7 @@
|
||||
"nodemon": "1.10.2",
|
||||
"nyc": "^11.8.0",
|
||||
"sorcery": "^0.10.0",
|
||||
"source-map-loader": "^0.2.3",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"ts-loader": "^4.5.0",
|
||||
"tslint": "^5.9.1",
|
||||
"tslint-consistent-codestyle": "^1.13.0",
|
||||
@@ -47,6 +47,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node demo/start",
|
||||
"start-debug": "node --inspect-brk demo/start",
|
||||
"start-zmodem": "node demo/zmodem/app",
|
||||
"lint": "tslint 'src/**/*.ts' './demo/**/*.ts'",
|
||||
"pretest": "npm run layering",
|
||||
|
||||
@@ -25,7 +25,10 @@ export interface ISearchOptions {
|
||||
regex?: boolean;
|
||||
wholeWord?: boolean;
|
||||
caseSensitive?: boolean;
|
||||
/** Assume caller implements 'search as you type' where findNext gets called when search input changes */
|
||||
/**
|
||||
* Use this when you want the selection to expand if it still matches as the
|
||||
* user types. Note that this only affects findNext.
|
||||
*/
|
||||
incremental?: boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ export class SearchHelper implements ISearchHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
let startCol: number = 0;
|
||||
let startRow = this._terminal._core.buffer.ydisp;
|
||||
|
||||
if (selectionManager.selectionEnd) {
|
||||
@@ -48,23 +49,30 @@ export class SearchHelper implements ISearchHelper {
|
||||
// For incremental search, use existing row
|
||||
if (this._terminal.getSelection().length !== 0) {
|
||||
startRow = incremental ? selectionManager.selectionStart[1] : selectionManager.selectionEnd[1];
|
||||
startCol = incremental ? selectionManager.selectionStart[0] : selectionManager.selectionEnd[0];
|
||||
}
|
||||
}
|
||||
|
||||
this._initLinesCache();
|
||||
|
||||
// Search from startRow to end
|
||||
for (let y = incremental ? startRow : startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
|
||||
result = this._findInLine(term, y, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
// Search startRow
|
||||
result = this._findInLine(term, startRow, startCol, searchOptions);
|
||||
|
||||
// Search from startRow + 1 to end
|
||||
if (!result) {
|
||||
for (let y = startRow + 1; y < this._terminal._core.buffer.ybase + this._terminal.rows; y++) {
|
||||
result = this._findInLine(term, y, 0, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search from the top to the startRow
|
||||
// Search from the top to the startRow (search the whole startRow again in
|
||||
// case startCol > 0)
|
||||
if (!result) {
|
||||
for (let y = 0; y < startRow; y++) {
|
||||
result = this._findInLine(term, y, searchOptions);
|
||||
for (let y = 0; y <= startRow; y++) {
|
||||
result = this._findInLine(term, y, 0, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
@@ -84,7 +92,6 @@ export class SearchHelper implements ISearchHelper {
|
||||
*/
|
||||
public findPrevious(term: string, searchOptions?: ISearchOptions): boolean {
|
||||
const selectionManager = this._terminal._core.selectionManager;
|
||||
const {incremental} = searchOptions;
|
||||
let result: ISearchResult;
|
||||
|
||||
if (!term || term.length === 0) {
|
||||
@@ -92,29 +99,39 @@ export class SearchHelper implements ISearchHelper {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isReverseSearch = true;
|
||||
let startRow = this._terminal._core.buffer.ydisp;
|
||||
let startCol: number = this._terminal._core.buffer.lines.get(startRow).length;
|
||||
|
||||
if (selectionManager.selectionStart) {
|
||||
// Start from the selection start if there is a selection
|
||||
if (this._terminal.getSelection().length !== 0) {
|
||||
startRow = selectionManager.selectionStart[1];
|
||||
startCol = selectionManager.selectionStart[0];
|
||||
}
|
||||
}
|
||||
|
||||
this._initLinesCache();
|
||||
|
||||
// Search from startRow to top
|
||||
for (let y = incremental ? startRow : startRow - 1; y >= 0; y--) {
|
||||
result = this._findInLine(term, y, searchOptions);
|
||||
if (result) {
|
||||
break;
|
||||
// Search startRow
|
||||
result = this._findInLine(term, startRow, startCol, searchOptions, isReverseSearch);
|
||||
|
||||
// Search from startRow - 1 to top
|
||||
if (!result) {
|
||||
for (let y = startRow - 1; y >= 0; y--) {
|
||||
result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Search from the bottom to startRow
|
||||
// Search from the bottom to startRow (search the whole startRow again in
|
||||
// case startCol > 0)
|
||||
if (!result) {
|
||||
for (let y = this._terminal._core.buffer.ybase + this._terminal.rows - 1; y > startRow; y--) {
|
||||
result = this._findInLine(term, y, searchOptions);
|
||||
const searchFrom = this._terminal._core.buffer.ybase + this._terminal.rows - 1;
|
||||
for (let y = searchFrom; y >= startRow; y--) {
|
||||
result = this._findInLine(term, y, this._terminal._core.buffer.lines.get(y).length, searchOptions, isReverseSearch);
|
||||
if (result) {
|
||||
break;
|
||||
}
|
||||
@@ -164,72 +181,88 @@ export class SearchHelper implements ISearchHelper {
|
||||
* started on an earlier line then it is skipped since it will be properly searched when the terminal line that the
|
||||
* text starts on is searched.
|
||||
* @param term The search term.
|
||||
* @param y The line to search.
|
||||
* @param row The line to start the search from.
|
||||
* @param col The column to start the search from.
|
||||
* @param searchOptions Search options.
|
||||
* @return The search result if it was found.
|
||||
*/
|
||||
protected _findInLine(term: string, y: number, searchOptions: ISearchOptions = {}): ISearchResult {
|
||||
if (this._terminal._core.buffer.lines.get(y).isWrapped) {
|
||||
protected _findInLine(term: string, row: number, col: number, searchOptions: ISearchOptions = {}, isReverseSearch: boolean = false): ISearchResult {
|
||||
if (this._terminal._core.buffer.lines.get(row).isWrapped) {
|
||||
return;
|
||||
}
|
||||
|
||||
let stringLine = this._linesCache ? this._linesCache[y] : void 0;
|
||||
let stringLine = this._linesCache ? this._linesCache[row] : void 0;
|
||||
if (stringLine === void 0) {
|
||||
stringLine = this.translateBufferLineToStringWithWrap(y, true);
|
||||
stringLine = this.translateBufferLineToStringWithWrap(row, true);
|
||||
if (this._linesCache) {
|
||||
this._linesCache[y] = stringLine;
|
||||
this._linesCache[row] = stringLine;
|
||||
}
|
||||
}
|
||||
|
||||
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
|
||||
const searchTerm = searchOptions.caseSensitive ? term : term.toLowerCase();
|
||||
let searchIndex = -1;
|
||||
const searchStringLine = searchOptions.caseSensitive ? stringLine : stringLine.toLowerCase();
|
||||
|
||||
let resultIndex = -1;
|
||||
if (searchOptions.regex) {
|
||||
const searchRegex = RegExp(searchTerm, 'g');
|
||||
const foundTerm = searchRegex.exec(searchStringLine);
|
||||
if (foundTerm && foundTerm[0].length > 0) {
|
||||
searchIndex = searchRegex.lastIndex - foundTerm[0].length;
|
||||
term = foundTerm[0];
|
||||
let foundTerm: RegExpExecArray;
|
||||
if (isReverseSearch) {
|
||||
// This loop will get the resultIndex of the _last_ regex match in the range 0..col
|
||||
while (foundTerm = searchRegex.exec(searchStringLine.slice(0, col))) {
|
||||
resultIndex = searchRegex.lastIndex - foundTerm[0].length;
|
||||
term = foundTerm[0];
|
||||
searchRegex.lastIndex -= (term.length - 1);
|
||||
}
|
||||
} else {
|
||||
foundTerm = searchRegex.exec(searchStringLine.slice(col));
|
||||
if (foundTerm && foundTerm[0].length > 0) {
|
||||
resultIndex = col + (searchRegex.lastIndex - foundTerm[0].length);
|
||||
term = foundTerm[0];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
searchIndex = searchStringLine.indexOf(searchTerm);
|
||||
if (isReverseSearch) {
|
||||
if (col - searchTerm.length >= 0) {
|
||||
resultIndex = searchStringLine.lastIndexOf(searchTerm, col - searchTerm.length);
|
||||
}
|
||||
} else {
|
||||
resultIndex = searchStringLine.indexOf(searchTerm, col);
|
||||
}
|
||||
}
|
||||
|
||||
if (searchIndex >= 0) {
|
||||
if (resultIndex >= 0) {
|
||||
// Adjust the row number and search index if needed since a "line" of text can span multiple rows
|
||||
if (searchIndex >= this._terminal.cols) {
|
||||
y += Math.floor(searchIndex / this._terminal.cols);
|
||||
searchIndex = searchIndex % this._terminal.cols;
|
||||
if (resultIndex >= this._terminal.cols) {
|
||||
row += Math.floor(resultIndex / this._terminal.cols);
|
||||
resultIndex = resultIndex % this._terminal.cols;
|
||||
}
|
||||
if (searchOptions.wholeWord && !this._isWholeWord(searchIndex, searchStringLine, term)) {
|
||||
if (searchOptions.wholeWord && !this._isWholeWord(resultIndex, searchStringLine, term)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const line = this._terminal._core.buffer.lines.get(y);
|
||||
const line = this._terminal._core.buffer.lines.get(row);
|
||||
|
||||
for (let i = 0; i < searchIndex; i++) {
|
||||
for (let i = 0; i < resultIndex; i++) {
|
||||
const charData = line.get(i);
|
||||
// Adjust the searchIndex to normalize emoji into single chars
|
||||
const char = charData[1/*CHAR_DATA_CHAR_INDEX*/];
|
||||
if (char.length > 1) {
|
||||
searchIndex -= char.length - 1;
|
||||
resultIndex -= char.length - 1;
|
||||
}
|
||||
// Adjust the searchIndex for empty characters following wide unicode
|
||||
// chars (eg. CJK)
|
||||
const charWidth = charData[2/*CHAR_DATA_WIDTH_INDEX*/];
|
||||
if (charWidth === 0) {
|
||||
searchIndex++;
|
||||
resultIndex++;
|
||||
}
|
||||
}
|
||||
return {
|
||||
term,
|
||||
col: searchIndex,
|
||||
row: y
|
||||
col: resultIndex,
|
||||
row
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a buffer line to a string, including subsequent lines if they are wraps.
|
||||
* Wide characters will count as two columns in the resulting string. This
|
||||
|
||||
@@ -29,8 +29,11 @@ class MockTerminal {
|
||||
}
|
||||
|
||||
class TestSearchHelper extends SearchHelper {
|
||||
public findInLine(term: string, y: number, searchOptions?: ISearchOptions): ISearchResult {
|
||||
return this._findInLine(term, y, searchOptions);
|
||||
public findInLine(term: string, rowNumber: number, searchOptions?: ISearchOptions): ISearchResult {
|
||||
return this._findInLine(term, rowNumber, 0, searchOptions);
|
||||
}
|
||||
public findFromIndex(term: string, row: number, col: number, searchOptions?: ISearchOptions, isReverseSearch?: boolean): ISearchResult {
|
||||
return this._findInLine(term, row, col, searchOptions, isReverseSearch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,5 +248,66 @@ describe('search addon', () => {
|
||||
expect(hello4).eql(undefined);
|
||||
expect(hello5).eql(undefined);
|
||||
});
|
||||
it('should find multiple matches in line', function(): void {
|
||||
search.apply(<any>MockTerminal);
|
||||
const term = new MockTerminal({cols: 20, rows: 5});
|
||||
term.core.write('helloooo helloooo\r\naaaAAaaAAA');
|
||||
term.pushWriteData();
|
||||
const searchOptions = {
|
||||
regex: false,
|
||||
wholeWord: false,
|
||||
caseSensitive: false
|
||||
};
|
||||
const find0 = term.searchHelper.findFromIndex('hello', 0, 0, searchOptions);
|
||||
const find1 = term.searchHelper.findFromIndex('hello', 0, find0.col + find0.term.length, searchOptions);
|
||||
const find2 = term.searchHelper.findFromIndex('aaaa', 1, 0, searchOptions);
|
||||
const find3 = term.searchHelper.findFromIndex('aaaa', 1, find2.col + find2.term.length, searchOptions);
|
||||
const find4 = term.searchHelper.findFromIndex('aaaa', 1, find3.col + find3.term.length, searchOptions);
|
||||
expect(find0).eql({col: 0, row: 0, term: 'hello'});
|
||||
expect(find1).eql({col: 9, row: 0, term: 'hello'});
|
||||
expect(find2).eql({col: 0, row: 1, term: 'aaaa'});
|
||||
expect(find3).eql({col: 4, row: 1, term: 'aaaa'});
|
||||
expect(find4).eql(undefined);
|
||||
});
|
||||
it('should find multiple matches in line - reverse search', function(): void {
|
||||
search.apply(<any>MockTerminal);
|
||||
const term = new MockTerminal({cols: 20, rows: 5});
|
||||
term.core.write('it is what it is');
|
||||
term.pushWriteData();
|
||||
const searchOptions = {
|
||||
regex: false,
|
||||
wholeWord: false,
|
||||
caseSensitive: false
|
||||
};
|
||||
const isReverseSearch = true;
|
||||
const find0 = term.searchHelper.findFromIndex('is', 0, 16, searchOptions, isReverseSearch);
|
||||
const find1 = term.searchHelper.findFromIndex('is', 0, find0.col, searchOptions, isReverseSearch);
|
||||
const find2 = term.searchHelper.findFromIndex('it', 0, 16, searchOptions, isReverseSearch);
|
||||
const find3 = term.searchHelper.findFromIndex('it', 0, find2.col, searchOptions, isReverseSearch);
|
||||
expect(find0).eql({col: 14, row: 0, term: 'is'});
|
||||
expect(find1).eql({col: 3, row: 0, term: 'is'});
|
||||
expect(find2).eql({col: 11, row: 0, term: 'it'});
|
||||
expect(find3).eql({col: 0, row: 0, term: 'it'});
|
||||
});
|
||||
it('should find multiple matches in line - reverse search with regex', function(): void {
|
||||
search.apply(<any>MockTerminal);
|
||||
const term = new MockTerminal({cols: 20, rows: 5});
|
||||
term.core.write('zzzABCzzzzABCABC');
|
||||
term.pushWriteData();
|
||||
const searchOptions = {
|
||||
regex: true,
|
||||
wholeWord: false,
|
||||
caseSensitive: true
|
||||
};
|
||||
const isReverseSearch = true;
|
||||
const find0 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, 16, searchOptions, isReverseSearch);
|
||||
const find1 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find0.col, searchOptions, isReverseSearch);
|
||||
const find2 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find1.col, searchOptions, isReverseSearch);
|
||||
const find3 = term.searchHelper.findFromIndex('[A-Z]{3}', 0, find2.col, searchOptions, isReverseSearch);
|
||||
expect(find0).eql({col: 13, row: 0, term: 'ABC'});
|
||||
expect(find1).eql({col: 10, row: 0, term: 'ABC'});
|
||||
expect(find2).eql({col: 3, row: 0, term: 'ABC'});
|
||||
expect(find3).eql(undefined);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,4 +63,28 @@ describe('webLinks addon', () => {
|
||||
|
||||
assert.equal(uri, 'http://foo.com/colon:test');
|
||||
});
|
||||
|
||||
it('should not allow " character at the end of a URI enclosed with ""', () => {
|
||||
const term = new MockTerminal();
|
||||
webLinks.webLinksInit(<any>term);
|
||||
|
||||
const row = '"http://foo.com/"';
|
||||
|
||||
const match = row.match(term.regex);
|
||||
const uri = match[term.options.matchIndex];
|
||||
|
||||
assert.equal(uri, 'http://foo.com/');
|
||||
});
|
||||
|
||||
it('should not allow \' character at the end of a URI enclosed with \'\'', () => {
|
||||
const term = new MockTerminal();
|
||||
webLinks.webLinksInit(<any>term);
|
||||
|
||||
const row = '\'http://foo.com/\'';
|
||||
|
||||
const match = row.match(term.regex);
|
||||
const uri = match[term.options.matchIndex];
|
||||
|
||||
assert.equal(uri, 'http://foo.com/');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
|
||||
const localHostClause = '(localhost)';
|
||||
const portClause = '(:\\d{1,5})';
|
||||
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
||||
const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:\\s])';
|
||||
const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:"\'\\s])';
|
||||
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
||||
|
||||
@@ -281,5 +281,16 @@ describe('Keyboard', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }).key, '\x1b[B');
|
||||
assert.equal(testEvaluateKeyboardEvent({ keyCode: 0, key: 'UIKeyInputDownArrow' }, { applicationCursorMode: true }).key, '\x1bOB');
|
||||
});
|
||||
|
||||
it('should handle lowercase letters', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ keyCode: 65, key: 'a' }).key, 'a');
|
||||
assert.equal(testEvaluateKeyboardEvent({ keyCode: 189, key: '-' }).key, '-');
|
||||
});
|
||||
|
||||
it('should handle uppercase letters', () => {
|
||||
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 65, key: 'A' }).key, 'A');
|
||||
assert.equal(testEvaluateKeyboardEvent({ shiftKey: true, keyCode: 49, key: '!' }).key, '!');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -349,6 +349,10 @@ export function evaluateKeyboardEvent(
|
||||
if (ev.keyCode === 65) { // cmd + a
|
||||
result.type = KeyboardResultType.SELECT_ALL;
|
||||
}
|
||||
} else if (ev.key && !ev.ctrlKey && !ev.altKey && !ev.metaKey &&
|
||||
ev.keyCode >= 48 && ev.keyCode !== 144 && ev.keyCode !== 145) {
|
||||
// Include only keys that that result in a character; don't include num lock and scroll lock
|
||||
result.key = ev.key;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export class CharMeasure extends EventEmitter implements ICharMeasure {
|
||||
}
|
||||
|
||||
public measure(options: ITerminalOptions): void {
|
||||
this._measureElement.style.fontFamily = options.fontFamily;
|
||||
this._measureElement.style.fontFamily = options.fontFamily;
|
||||
this._measureElement.style.fontSize = `${options.fontSize}px`;
|
||||
const geometry = this._measureElement.getBoundingClientRect();
|
||||
// The element is likely currently display:none, we should retain the
|
||||
|
||||
@@ -3839,16 +3839,6 @@ loader-utils@^1.0.2, loader-utils@^1.1.0:
|
||||
emojis-list "^2.0.0"
|
||||
json5 "^0.5.0"
|
||||
|
||||
loader-utils@~0.2.2:
|
||||
version "0.2.17"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348"
|
||||
integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=
|
||||
dependencies:
|
||||
big.js "^3.1.3"
|
||||
emojis-list "^2.0.0"
|
||||
json5 "^0.5.0"
|
||||
object-assign "^4.0.1"
|
||||
|
||||
locate-path@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
|
||||
@@ -5972,14 +5962,13 @@ source-list-map@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
|
||||
integrity sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==
|
||||
|
||||
source-map-loader@^0.2.3:
|
||||
version "0.2.3"
|
||||
resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.3.tgz#d4b0c8cd47d54edce3e6bfa0f523f452b5b0e521"
|
||||
integrity sha512-MYbFX9DYxmTQFfy2v8FC1XZwpwHKYxg3SK8Wb7VPBKuhDjz8gi9re2819MsG4p49HDyiOSUKlmZ+nQBArW5CGw==
|
||||
source-map-loader@^0.2.4:
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-0.2.4.tgz#c18b0dc6e23bf66f6792437557c569a11e072271"
|
||||
integrity sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==
|
||||
dependencies:
|
||||
async "^2.5.0"
|
||||
loader-utils "~0.2.2"
|
||||
source-map "~0.6.1"
|
||||
loader-utils "^1.1.0"
|
||||
|
||||
source-map-resolve@^0.5.0, source-map-resolve@^0.5.1:
|
||||
version "0.5.2"
|
||||
|
||||
Reference in New Issue
Block a user