Skip to content

Commit

Permalink
Merge pull request #704 from Tyriar/553_find_api
Browse files Browse the repository at this point in the history
Implement very basic find API
  • Loading branch information
Tyriar authored Jul 13, 2017
2 parents 2ebc926 + 43b47ab commit 87b76aa
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 60 deletions.
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 @@ -163,6 +164,9 @@ export class SelectionManager extends EventEmitter {
this.clearSelection();
}

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

/**
* Gets whether there is an active text selection.
*/
Expand All @@ -188,12 +192,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 @@ -204,7 +208,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 @@ -230,55 +234,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 @@ -565,13 +520,21 @@ export class SelectionManager extends EventEmitter {
return charIndex;
}

public setSelection(col: number, row: number, length: number): void {
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

0 comments on commit 87b76aa

Please sign in to comment.