Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement very basic find API #704

Merged
merged 25 commits into from
Jul 13, 2017
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8d12881
Very basic find implementation
Tyriar Jun 12, 2017
280b698
More work on findNext
Tyriar Jun 13, 2017
d3f5a06
Fix search scrolling up
Tyriar Jun 14, 2017
7d8f698
Add findPrevious
Tyriar Jun 14, 2017
f88dcd1
Expose API on Terminal
Tyriar Jun 14, 2017
b957130
Consolidate translateBufferLineToString functions
Tyriar Jun 14, 2017
5360277
Merge remote-tracking branch 'origin/master' into 553_find_api
Tyriar Jun 14, 2017
528f06b
Remove log
Tyriar Jun 14, 2017
39151e4
Merge branch 'master' into 553_find_api
Tyriar Jun 15, 2017
6854def
Use case-insensitive search by default
Tyriar Jun 21, 2017
f93b24a
Merge branch 'master' into 553_find_api
Tyriar Jun 21, 2017
1938145
Get find working as an addon
Tyriar Jun 23, 2017
baccca8
Merge branch 'master' into 553_find_api
Tyriar Jun 23, 2017
11de0d0
Merge remote-tracking branch 'ups/master' into 553_find_api
Tyriar Jul 9, 2017
80f7a37
Point to search addon properly
Tyriar Jul 9, 2017
3c0e142
Fix sourcemaps
Tyriar Jul 9, 2017
fab039d
Rearrange module loader to work with browserify
Tyriar Jul 9, 2017
4b0cf2f
jsdoc
Tyriar Jul 9, 2017
2564592
Change case of search addon files
Tyriar Jul 9, 2017
c13b86a
Add find to demo
Tyriar Jul 9, 2017
c62923f
Change case of browserify-addons files
Tyriar Jul 9, 2017
73970ec
Fix casing of search.js
Tyriar Jul 9, 2017
4c0e7b2
Merge remote-tracking branch 'ups/master' into 553_find_api
Tyriar Jul 9, 2017
af58643
Merge branch 'master' into 553_find_api
Tyriar Jul 13, 2017
43b47ab
Merge branch 'master' into 553_find_api
Tyriar Jul 13, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,18 @@
<script src="/build/addons/attach/attach.js" ></script>
<script src="/build/addons/fit/fit.js" ></script>
<script src="/build/addons/fullscreen/fullscreen.js" ></script>
<script src="/build/addons/search/search.js" ></script>
</head>
<body>
<h1>xterm.js: xterm, in the browser</h1>
<div id="terminal-container"></div>
<div>
<h2>Actions</h2>
<p>
<label>Find next <input id="find-next"/></label>
<label>Find previous <input id="find-previous"/></label>
</p>
</div>
<div>
<h2>Options</h2>
<p>
Expand Down
17 changes: 17 additions & 0 deletions demo/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ var term,
charHeight;

var terminalContainer = document.getElementById('terminal-container'),
actionElements = {
findNext: document.querySelector('#find-next'),
findPrevious: document.querySelector('#find-previous')
},
optionElements = {
cursorBlink: document.querySelector('#option-cursor-blink'),
cursorStyle: document.querySelector('#option-cursor-style'),
Expand All @@ -30,6 +34,19 @@ function setTerminalSize () {
colsElement.addEventListener('change', setTerminalSize);
rowsElement.addEventListener('change', setTerminalSize);

actionElements.findNext.addEventListener('keypress', function (e) {
if (e.key === "Enter") {
e.preventDefault();
term.findNext(actionElements.findNext.value);
}
});
actionElements.findPrevious.addEventListener('keypress', function (e) {
if (e.key === "Enter") {
e.preventDefault();
term.findPrevious(actionElements.findPrevious.value);
}
});

optionElements.cursorBlink.addEventListener('change', function () {
term.setOption('cursorBlink', optionElements.cursorBlink.checked);
});
Expand Down
54 changes: 47 additions & 7 deletions gulpfile.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
/**
* @license MIT
*/

const browserify = require('browserify');
const buffer = require('vinyl-buffer');
const coveralls = require('gulp-coveralls');
Expand All @@ -14,6 +18,7 @@ const ts = require('gulp-typescript');

let buildDir = process.env.BUILD_DIR || 'build';
let tsProject = ts.createProject('tsconfig.json');
let tsProjectSearchAddon = ts.createProject('./src/addons/search/tsconfig.json');
let srcDir = tsProject.config.compilerOptions.rootDir;
let outDir = tsProject.config.compilerOptions.outDir;

Expand All @@ -30,13 +35,17 @@ gulp.task('tsc', function () {
let tsResult = tsProject.src().pipe(sourcemaps.init()).pipe(tsProject());
let tsc = tsResult.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(outDir));

fs.emptyDirSync(`${outDir}/addons/search`);
let tsResultSearchAddon = tsProjectSearchAddon.src().pipe(sourcemaps.init()).pipe(tsProjectSearchAddon());
let tscSearchAddon = tsResultSearchAddon.js.pipe(sourcemaps.write('.', {includeContent: false, sourceRoot: ''})).pipe(gulp.dest(`${outDir}/addons/search`));

// Copy all addons from ${srcDir}/ to ${outDir}/
let copyAddons = gulp.src(`${srcDir}/addons/**/*`).pipe(gulp.dest(`${outDir}/addons`));
let copyAddons = gulp.src([`${srcDir}/addons/**/*`, `!${srcDir}/addons/search`, `!${srcDir}/addons/search/**`]).pipe(gulp.dest(`${outDir}/addons`));

// Copy stylesheets from ${srcDir}/ to ${outDir}/
let copyStylesheets = gulp.src(`${srcDir}/**/*.css`).pipe(gulp.dest(outDir));

return merge(tsc, copyAddons, copyStylesheets);
return merge(tsc, tscSearchAddon, copyAddons, copyStylesheets);
});

/**
Expand All @@ -63,13 +72,38 @@ gulp.task('browserify', ['tsc'], function() {
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(buildDir));

// Copy all add-ons from ${outDir}/ to buildDir
let copyAddons = gulp.src(`${outDir}/addons/**/*`).pipe(gulp.dest(`${buildDir}/addons`));

// Copy stylesheets from ${outDir}/ to ${buildDir}/
let copyStylesheets = gulp.src(`${outDir}/**/*.css`).pipe(gulp.dest(buildDir));

return merge(bundleStream, copyAddons, copyStylesheets);
return merge(bundleStream, copyStylesheets);
});

gulp.task('browserify-addons', ['tsc'], function() {
let searchOptions = {
basedir: `${buildDir}/addons/search`,
debug: true,
entries: [`${outDir}/addons/search/search.js`],
cache: {},
packageCache: {}
};
let searchBundle = browserify(searchOptions)
.bundle()
.pipe(source('./addons/search/search.js'))
.pipe(buffer())
.pipe(sourcemaps.init({loadMaps: true, sourceRoot: ''}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(buildDir));

// Copy all add-ons from outDir to buildDir
let copyAddons = gulp.src([
// Copy JS addons
`${outDir}/addons/**/*`,
// Exclude TS addons from copy as they are being built via browserify
`!${outDir}/addons/search`,
`!${outDir}/addons/search/**`
]).pipe(gulp.dest(`${buildDir}/addons`));

return merge(searchBundle, copyAddons);
});

gulp.task('instrument-test', function () {
Expand Down Expand Up @@ -98,6 +132,12 @@ gulp.task('sorcery', ['browserify'], function () {
chain.writeSync();
});

gulp.task('sorcery-addons', ['browserify-addons'], function () {
var chain = sorcery.loadSync(`${buildDir}/addons/search/search.js`);
chain.apply();
chain.writeSync();
});

/**
* Submit coverage results to coveralls.io
*/
Expand All @@ -106,6 +146,6 @@ gulp.task('coveralls', function () {
.pipe(coveralls());
});

gulp.task('build', ['sorcery']);
gulp.task('build', ['sorcery', 'sorcery-addons']);
gulp.task('test', ['mocha']);
gulp.task('default', ['build']);
5 changes: 5 additions & 0 deletions src/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface ITerminal {
element: HTMLElement;
rowContainer: HTMLElement;
selectionContainer: HTMLElement;
selectionManager: ISelectionManager;
charMeasure: ICharMeasure;
textarea: HTMLTextAreaElement;
ybase: number;
Expand Down Expand Up @@ -51,6 +52,10 @@ export interface ITerminal {

export interface ISelectionManager {
selectionText: string;
selectionStart: [number, number];
selectionEnd: [number, number];

setSelection(row: number, col: number, length: number);
}

export interface ICharMeasure {
Expand Down
69 changes: 16 additions & 53 deletions src/SelectionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CircularList } from './utils/CircularList';
import { EventEmitter } from './EventEmitter';
import { ITerminal } from './Interfaces';
import { SelectionModel } from './SelectionModel';
import { translateBufferLineToString } from './utils/BufferLine';

/**
* The number of pixels the mouse needs to be above or below the viewport in
Expand Down Expand Up @@ -181,6 +182,9 @@ export class SelectionManager extends EventEmitter {
this.clearSelection();
}

public get selectionStart(): [number, number] { return this._model.finalSelectionStart; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you document these with JSDoc please?

public get selectionEnd(): [number, number] { return this._model.finalSelectionEnd; }

/**
* Gets whether there is an active text selection.
*/
Expand All @@ -206,12 +210,12 @@ export class SelectionManager extends EventEmitter {
// Get first row
const startRowEndCol = start[1] === end[1] ? end[0] : null;
let result: string[] = [];
result.push(this._translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol));
result.push(translateBufferLineToString(this._buffer.get(start[1]), true, start[0], startRowEndCol));

// Get middle rows
for (let i = start[1] + 1; i <= end[1] - 1; i++) {
const bufferLine = this._buffer.get(i);
const lineText = this._translateBufferLineToString(bufferLine, true);
const lineText = translateBufferLineToString(bufferLine, true);
if (bufferLine.isWrapped) {
result[result.length - 1] += lineText;
} else {
Expand All @@ -222,7 +226,7 @@ export class SelectionManager extends EventEmitter {
// Get final row
if (start[1] !== end[1]) {
const bufferLine = this._buffer.get(end[1]);
const lineText = this._translateBufferLineToString(bufferLine, true, 0, end[0]);
const lineText = translateBufferLineToString(bufferLine, true, 0, end[0]);
if (bufferLine.isWrapped) {
result[result.length - 1] += lineText;
} else {
Expand All @@ -248,55 +252,6 @@ export class SelectionManager extends EventEmitter {
this.refresh();
}

/**
* Translates a buffer line to a string, with optional start and end columns.
* Wide characters will count as two columns in the resulting string. This
* function is useful for getting the actual text underneath the raw selection
* position.
* @param line The line being translated.
* @param trimRight Whether to trim whitespace to the right.
* @param startCol The column to start at.
* @param endCol The column to end at.
*/
private _translateBufferLineToString(line: any, trimRight: boolean, startCol: number = 0, endCol: number = null): string {
// TODO: This function should live in a buffer or buffer line class

// Get full line
let lineString = '';
let widthAdjustedStartCol = startCol;
let widthAdjustedEndCol = endCol;
for (let i = 0; i < line.length; i++) {
const char = line[i];
lineString += char[LINE_DATA_CHAR_INDEX];
// Adjust start and end cols for wide characters if they affect their
// column indexes
if (char[LINE_DATA_WIDTH_INDEX] === 0) {
if (startCol >= i) {
widthAdjustedStartCol--;
}
if (endCol >= i) {
widthAdjustedEndCol--;
}
}
}

// Calculate the final end col by trimming whitespace on the right of the
// line if needed.
let finalEndCol = widthAdjustedEndCol || line.length;
if (trimRight) {
const rightWhitespaceIndex = lineString.search(/\s+$/);
if (rightWhitespaceIndex !== -1) {
finalEndCol = Math.min(finalEndCol, rightWhitespaceIndex);
}
// Return the empty string if only trimmed whitespace is selected
if (finalEndCol <= widthAdjustedStartCol) {
return '';
}
}

return lineString.substring(widthAdjustedStartCol, finalEndCol);
}

/**
* Queues a refresh, redrawing the selection on the next opportunity.
* @param isNewSelection Whether the selection should be registered as a new
Expand Down Expand Up @@ -611,13 +566,21 @@ export class SelectionManager extends EventEmitter {
return charIndex;
}

public setSelection(col: number, row: number, length: number): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you document this with JSDoc please?

this._model.clearSelection();
this._removeMouseDownListeners();
this._model.selectionStart = [col, row];
this._model.selectionStartLength = length;
this.refresh();
}

/**
* Gets positional information for the word at the coordinated specified.
* @param coords The coordinates to get the word at.
*/
private _getWordAt(coords: [number, number]): IWordPosition {
const bufferLine = this._buffer.get(coords[1]);
const line = this._translateBufferLineToString(bufferLine, false);
const line = translateBufferLineToString(bufferLine, false);

// Get actual index, taking into consideration wide characters
let endIndex = this._convertViewportColToCharacterIndex(bufferLine, coords);
Expand Down
Loading