mindoc/static/table-editor/dist/index.js

3828 lines
122 KiB
Go
Raw Normal View History

(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define("TableEditor", [], factory);
else if(typeof exports === 'object')
exports["TableEditor"] = factory();
else
root["TableEditor"] = factory();
})(typeof self !== 'undefined' ? self : this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony export (immutable) */ __webpack_exports__["initTableEditor"] = initTableEditor;
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__ = __webpack_require__(1);
/* global CodeMirror, $ */
// port of the code from: https://github.com/susisu/mte-demo/blob/master/src/main.js
// text editor interface
// see https://doc.esdoc.org/github.com/susisu/mte-kernel/class/lib/text-editor.js~ITextEditor.html
class TextEditorInterface {
constructor (editor) {
this.editor = editor
this.doc = editor.getDoc()
this.transaction = false
this.onDidFinishTransaction = null
}
getCursorPosition () {
const { line, ch } = this.doc.getCursor()
return new __WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["c" /* Point */](line, ch)
}
setCursorPosition (pos) {
this.doc.setCursor({ line: pos.row, ch: pos.column })
}
setSelectionRange (range) {
this.doc.setSelection(
{ line: range.start.row, ch: range.start.column },
{ line: range.end.row, ch: range.end.column }
)
}
getLastRow () {
return this.doc.lineCount() - 1
}
acceptsTableEdit () {
return true
}
getLine (row) {
return this.doc.getLine(row)
}
insertLine (row, line) {
const lastRow = this.getLastRow()
if (row > lastRow) {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'\n' + line,
{ line: lastRow, ch: lastLine.length },
{ line: lastRow, ch: lastLine.length }
)
} else {
this.doc.replaceRange(
line + '\n',
{ line: row, ch: 0 },
{ line: row, ch: 0 }
)
}
}
deleteLine (row) {
const lastRow = this.getLastRow()
if (row >= lastRow) {
if (lastRow > 0) {
const preLastLine = this.getLine(lastRow - 1)
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'',
{ line: lastRow - 1, ch: preLastLine.length },
{ line: lastRow, ch: lastLine.length }
)
} else {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
'',
{ line: lastRow, ch: 0 },
{ line: lastRow, ch: lastLine.length }
)
}
} else {
this.doc.replaceRange(
'',
{ line: row, ch: 0 },
{ line: row + 1, ch: 0 }
)
}
}
replaceLines (startRow, endRow, lines) {
const lastRow = this.getLastRow()
if (endRow > lastRow) {
const lastLine = this.getLine(lastRow)
this.doc.replaceRange(
lines.join('\n'),
{ line: startRow, ch: 0 },
{ line: lastRow, ch: lastLine.length }
)
} else {
this.doc.replaceRange(
lines.join('\n') + '\n',
{ line: startRow, ch: 0 },
{ line: endRow, ch: 0 }
)
}
}
transact (func) {
this.transaction = true
func()
this.transaction = false
if (this.onDidFinishTransaction) {
this.onDidFinishTransaction.call(undefined)
}
}
}
function initTableEditor (editor) {
// create an interface to the text editor
const editorIntf = new TextEditorInterface(editor)
// create a table editor object
const tableEditor = new __WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["d" /* TableEditor */](editorIntf)
// options for the table editor
const opts = Object(__WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["e" /* options */])({
smartCursor: true,
formatType: __WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["b" /* FormatType */].NORMAL
})
// keymap of the commands
// from https://github.com/susisu/mte-demo/blob/master/src/main.js
const keyMap = CodeMirror.normalizeKeyMap({
Tab: () => { tableEditor.nextCell(opts) },
'Shift-Tab': () => { tableEditor.previousCell(opts) },
Enter: () => { tableEditor.nextRow(opts) },
'Ctrl-Enter': () => { tableEditor.escape(opts) },
'Cmd-Enter': () => { tableEditor.escape(opts) },
'Shift-Ctrl-Left': () => { tableEditor.alignColumn(__WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["a" /* Alignment */].LEFT, opts) },
'Shift-Cmd-Left': () => { tableEditor.alignColumn(__WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["a" /* Alignment */].LEFT, opts) },
'Shift-Ctrl-Right': () => { tableEditor.alignColumn(__WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["a" /* Alignment */].RIGHT, opts) },
'Shift-Cmd-Right': () => { tableEditor.alignColumn(__WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["a" /* Alignment */].RIGHT, opts) },
'Shift-Ctrl-Up': () => { tableEditor.alignColumn(__WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["a" /* Alignment */].CENTER, opts) },
'Shift-Cmd-Up': () => { tableEditor.alignColumn(__WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["a" /* Alignment */].CENTER, opts) },
'Shift-Ctrl-Down': () => { tableEditor.alignColumn(__WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["a" /* Alignment */].NONE, opts) },
'Shift-Cmd-Down': () => { tableEditor.alignColumn(__WEBPACK_IMPORTED_MODULE_0__susisu_mte_kernel__["a" /* Alignment */].NONE, opts) },
'Ctrl-Left': () => { tableEditor.moveFocus(0, -1, opts) },
'Cmd-Left': () => { tableEditor.moveFocus(0, -1, opts) },
'Ctrl-Right': () => { tableEditor.moveFocus(0, 1, opts) },
'Cmd-Right': () => { tableEditor.moveFocus(0, 1, opts) },
'Ctrl-Up': () => { tableEditor.moveFocus(-1, 0, opts) },
'Cmd-Up': () => { tableEditor.moveFocus(-1, 0, opts) },
'Ctrl-Down': () => { tableEditor.moveFocus(1, 0, opts) },
'Cmd-Down': () => { tableEditor.moveFocus(1, 0, opts) },
'Ctrl-K Ctrl-I': () => { tableEditor.insertRow(opts) },
'Cmd-K Cmd-I': () => { tableEditor.insertRow(opts) },
'Ctrl-L Ctrl-I': () => { tableEditor.deleteRow(opts) },
'Cmd-L Cmd-I': () => { tableEditor.deleteRow(opts) },
'Ctrl-K Ctrl-J': () => { tableEditor.insertColumn(opts) },
'Cmd-K Cmd-J': () => { tableEditor.insertColumn(opts) },
'Ctrl-L Ctrl-J': () => { tableEditor.deleteColumn(opts) },
'Cmd-L Cmd-J': () => { tableEditor.deleteColumn(opts) },
'Alt-Shift-Ctrl-Left': () => { tableEditor.moveColumn(-1, opts) },
'Alt-Shift-Cmd-Left': () => { tableEditor.moveColumn(-1, opts) },
'Alt-Shift-Ctrl-Right': () => { tableEditor.moveColumn(1, opts) },
'Alt-Shift-Cmd-Right': () => { tableEditor.moveColumn(1, opts) },
'Alt-Shift-Ctrl-Up': () => { tableEditor.moveRow(-1, opts) },
'Alt-Shift-Cmd-Up': () => { tableEditor.moveRow(-1, opts) },
'Alt-Shift-Ctrl-Down': () => { tableEditor.moveRow(1, opts) },
'Alt-Shift-Cmd-Down': () => { tableEditor.moveRow(1, opts) }
})
// enable keymap if the cursor is in a table
function updateActiveState() {
const active = tableEditor.cursorIsInTable();
if (active) {
editor.setOption("extraKeys", keyMap);
}
else {
editor.setOption("extraKeys", null);
tableEditor.resetSmartCursor();
}
}
// event subscriptions
editor.on("cursorActivity", () => {
if (!editorIntf.transaction) {
updateActiveState();
}
});
editor.on("changes", () => {
if (!editorIntf.transaction) {
updateActiveState();
}
});
editorIntf.onDidFinishTransaction = () => {
updateActiveState();
};
return tableEditor
}
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "c", function() { return Point; });
/* unused harmony export Range */
/* unused harmony export Focus */
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Alignment; });
/* unused harmony export DefaultAlignment */
/* unused harmony export HeaderAlignment */
/* unused harmony export TableCell */
/* unused harmony export TableRow */
/* unused harmony export Table */
/* unused harmony export readTable */
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return FormatType; });
/* unused harmony export completeTable */
/* unused harmony export formatTable */
/* unused harmony export alterAlignment */
/* unused harmony export insertRow */
/* unused harmony export deleteRow */
/* unused harmony export moveRow */
/* unused harmony export insertColumn */
/* unused harmony export deleteColumn */
/* unused harmony export moveColumn */
/* unused harmony export Insert */
/* unused harmony export Delete */
/* unused harmony export applyEditScript */
/* unused harmony export shortestEditScript */
/* unused harmony export ITextEditor */
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "e", function() { return options; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "d", function() { return TableEditor; });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_meaw__ = __webpack_require__(2);
/**
* A `Point` represents a point in the text editor.
*/
class Point {
/**
* Creates a new `Point` object.
*
* @param {number} row - Row of the point, starts from 0.
* @param {number} column - Column of the point, starts from 0.
*/
constructor(row, column) {
/** @private */
this._row = row;
/** @private */
this._column = column;
}
/**
* Row of the point.
*
* @type {number}
*/
get row() {
return this._row;
}
/**
* Column of the point.
*
* @type {number}
*/
get column() {
return this._column;
}
/**
* Checks if the point is equal to another point.
*
* @param {Point} point - A point object.
* @returns {boolean} `true` if two points are equal.
*/
equals(point) {
return this.row === point.row && this.column === point.column;
}
}
/**
* A `Range` object represents a range in the text editor.
*/
class Range {
/**
* Creates a new `Range` object.
*
* @param {Point} start - The start point of the range.
* @param {Point} end - The end point of the range.
*/
constructor(start, end) {
/** @private */
this._start = start;
/** @private */
this._end = end;
}
/**
* The start point of the range.
*
* @type {Point}
*/
get start() {
return this._start;
}
/**
* The end point of the range.
*
* @type {Point}
*/
get end() {
return this._end;
}
}
/**
* A `Focus` object represents which cell is focused in the table.
*
* Note that `row` and `column` properties specifiy a cell's position in the table, not the cursor's
* position in the text editor as {@link Point} class.
*
* @private
*/
class Focus {
/**
* Creates a new `Focus` object.
*
* @param {number} row - Row of the focused cell.
* @param {number} column - Column of the focused cell.
* @param {number} offset - Raw offset in the cell.
*/
constructor(row, column, offset) {
/** @private */
this._row = row;
/** @private */
this._column = column;
/** @private */
this._offset = offset;
}
/**
* Row of the focused cell.
*
* @type {number}
*/
get row() {
return this._row;
}
/**
* Column of the focused cell.
*
* @type {number}
*/
get column() {
return this._column;
}
/**
* Raw offset in the cell.
*
* @type {number}
*/
get offset() {
return this._offset;
}
/**
* Checks if two focuses point the same cell.
* Offsets are ignored.
*
* @param {Focus} focus - A focus object.
* @returns {boolean}
*/
posEquals(focus) {
return this.row === focus.row && this.column === focus.column;
}
/**
* Creates a copy of the focus object by setting its row to the specified value.
*
* @param {number} row - Row of the focused cell.
* @returns {Focus} A new focus object with the specified row.
*/
setRow(row) {
return new Focus(row, this.column, this.offset);
}
/**
* Creates a copy of the focus object by setting its column to the specified value.
*
* @param {number} column - Column of the focused cell.
* @returns {Focus} A new focus object with the specified column.
*/
setColumn(column) {
return new Focus(this.row, column, this.offset);
}
/**
* Creates a copy of the focus object by setting its offset to the specified value.
*
* @param {number} offset - Offset in the focused cell.
* @returns {Focus} A new focus object with the specified offset.
*/
setOffset(offset) {
return new Focus(this.row, this.column, offset);
}
}
/**
* Represents column alignment.
*
* - `Alignment.NONE` - Use default alignment.
* - `Alignment.LEFT` - Align left.
* - `Alignment.RIGHT` - Align right.
* - `Alignment.CENTER` - Align center.
*
* @type {Object}
*/
const Alignment = Object.freeze({
NONE : "none",
LEFT : "left",
RIGHT : "right",
CENTER: "center"
});
/**
* Represents default column alignment
*
* - `DefaultAlignment.LEFT` - Align left.
* - `DefaultAlignment.RIGHT` - Align right.
* - `DefaultAlignment.CENTER` - Align center.
*
* @type {Object}
*/
const DefaultAlignment = Object.freeze({
LEFT : Alignment.LEFT,
RIGHT : Alignment.RIGHT,
CENTER: Alignment.CENTER
});
/**
* Represents alignment of header cells.
*
* - `HeaderAlignment.FOLLOW` - Follow column's alignment.
* - `HeaderAlignment.LEFT` - Align left.
* - `HeaderAlignment.RIGHT` - Align right.
* - `HeaderAlignment.CENTER` - Align center.
*
* @type {Object}
*/
const HeaderAlignment = Object.freeze({
FOLLOW: "follow",
LEFT : Alignment.LEFT,
RIGHT : Alignment.RIGHT,
CENTER: Alignment.CENTER
});
/**
* A `TableCell` object represents a table cell.
*
* @private
*/
class TableCell {
/**
* Creates a new `TableCell` object.
*
* @param {string} rawContent - Raw content of the cell.
*/
constructor(rawContent) {
/** @private */
this._rawContent = rawContent;
/** @private */
this._content = rawContent.trim();
/** @private */
this._paddingLeft = this._content === ""
? (this._rawContent === "" ? 0 : 1)
: this._rawContent.length - this._rawContent.trimLeft().length;
/** @private */
this._paddingRight = this._rawContent.length - this._content.length - this._paddingLeft;
}
/**
* Raw content of the cell.
*
* @type {string}
*/
get rawContent() {
return this._rawContent;
}
/**
* Trimmed content of the cell.
*
* @type {string}
*/
get content() {
return this._content;
}
/**
* Width of the left padding of the cell.
*
* @type {number}
*/
get paddingLeft() {
return this._paddingLeft;
}
/**
* Width of the right padding of the cell.
*
* @type {number}
*/
get paddingRight() {
return this._paddingRight;
}
/**
* Convers the cell to a text representation.
*
* @returns {string} The raw content of the cell.
*/
toText() {
return this.rawContent;
}
/**
* Checks if the cell is a delimiter i.e. it only contains hyphens `-` with optional one
* leading and trailing colons `:`.
*
* @returns {boolean} `true` if the cell is a delimiter.
*/
isDelimiter() {
return /^\s*:?-+:?\s*$/.test(this.rawContent);
}
/**
* Returns the alignment the cell represents.
*
* @returns {Alignment|undefined} The alignment the cell represents;
* `undefined` if the cell is not a delimiter.
*/
getAlignment() {
if (!this.isDelimiter()) {
return undefined;
}
if (this.content[0] === ":") {
if (this.content[this.content.length - 1] === ":") {
return Alignment.CENTER;
}
else {
return Alignment.LEFT;
}
}
else {
if (this.content[this.content.length - 1] === ":") {
return Alignment.RIGHT;
}
else {
return Alignment.NONE;
}
}
}
/**
* Computes a relative position in the trimmed content from that in the raw content.
*
* @param {number} rawOffset - Relative position in the raw content.
* @returns {number} - Relative position in the trimmed content.
*/
computeContentOffset(rawOffset) {
if (this.content === "") {
return 0;
}
if (rawOffset < this.paddingLeft) {
return 0;
}
if (rawOffset < this.paddingLeft + this.content.length) {
return rawOffset - this.paddingLeft;
}
else {
return this.content.length;
}
}
/**
* Computes a relative position in the raw content from that in the trimmed content.
*
* @param {number} contentOffset - Relative position in the trimmed content.
* @returns {number} - Relative position in the raw content.
*/
computeRawOffset(contentOffset) {
return contentOffset + this.paddingLeft;
}
}
/**
* A `TableRow` object represents a table row.
*
* @private
*/
class TableRow {
/**
* Creates a new `TableRow` objec.
*
* @param {Array<TableCell>} cells - Cells that the row contains.
* @param {string} marginLeft - Margin string at the left of the row.
* @param {string} marginRight - Margin string at the right of the row.
*/
constructor(cells, marginLeft, marginRight) {
/** @private */
this._cells = cells.slice();
/** @private */
this._marginLeft = marginLeft;
/** @private */
this._marginRight = marginRight;
}
/**
* Margin string at the left of the row.
*
* @type {string}
*/
get marginLeft() {
return this._marginLeft;
}
/**
* Margin string at the right of the row.
*
* @type {string}
*/
get marginRight() {
return this._marginRight;
}
/**
* Gets the number of the cells in the row.
*
* @returns {number} Number of the cells.
*/
getWidth() {
return this._cells.length;
}
/**
* Returns the cells that the row contains.
*
* @returns {Array<TableCell>} An array of cells that the row contains.
*/
getCells() {
return this._cells.slice();
}
/**
* Gets a cell at the specified index.
*
* @param {number} index - Index.
* @returns {TableCell|undefined} The cell at the specified index if exists;
* `undefined` if no cell is found.
*/
getCellAt(index) {
return this._cells[index];
}
/**
* Convers the row to a text representation.
*
* @returns {string} A text representation of the row.
*/
toText() {
if (this._cells.length === 0) {
return this.marginLeft;
}
else {
const cells = this._cells.map(cell => cell.toText()).join("|");
return `${this.marginLeft}|${cells}|${this.marginRight}`;
}
}
/**
* Checks if the row is a delimiter or not.
*
* @returns {boolean} `true` if the row is a delimiter i.e. all the cells contained are delimiters.
*/
isDelimiter() {
return this._cells.every(cell => cell.isDelimiter());
}
}
/**
* A `Table` object represents a table.
*
* @private
*/
class Table {
/**
* Creates a new `Table` object.
*
* @param {Array<TableRow>} rows - An array of rows that the table contains.
*/
constructor(rows) {
/** @private */
this._rows = rows.slice();
}
/**
* Gets the number of rows in the table.
*
* @returns {number} The number of rows.
*/
getHeight() {
return this._rows.length;
}
/**
* Gets the maximum width of the rows in the table.
*
* @returns {number} The maximum width of the rows.
*/
getWidth() {
return this._rows.map(row => row.getWidth())
.reduce((x, y) => Math.max(x, y), 0);
}
/**
* Gets the width of the header row.
*
* @returns {number|undefined} The width of the header row;
* `undefined` if there is no header row.
*/
getHeaderWidth() {
if (this._rows.length === 0) {
return undefined;
}
return this._rows[0].getWidth();
}
/**
* Gets the rows that the table contains.
*
* @returns {Array<TableRow>} An array of the rows.
*/
getRows() {
return this._rows.slice();
}
/**
* Gets a row at the specified index.
*
* @param {number} index - Row index.
* @returns {TableRow|undefined} The row at the specified index;
* `undefined` if not found.
*/
getRowAt(index) {
return this._rows[index];
}
/**
* Gets the delimiter row of the table.
*
* @returns {TableRow|undefined} The delimiter row;
* `undefined` if there is not delimiter row.
*/
getDelimiterRow() {
const row = this._rows[1];
if (row === undefined) {
return undefined;
}
if (row.isDelimiter()) {
return row;
}
else {
return undefined;
}
}
/**
* Gets a cell at the specified index.
*
* @param {number} rowIndex - Row index of the cell.
* @param {number} columnIndex - Column index of the cell.
* @returns {TableCell|undefined} The cell at the specified index;
* `undefined` if not found.
*/
getCellAt(rowIndex, columnIndex) {
const row = this._rows[rowIndex];
if (row === undefined) {
return undefined;
}
return row.getCellAt(columnIndex);
}
/**
* Gets the cell at the focus.
*
* @param {Focus} focus - Focus object.
* @returns {TableCell|undefined} The cell at the focus;
* `undefined` if not found.
*/
getFocusedCell(focus) {
return this.getCellAt(focus.row, focus.column);
}
/**
* Converts the table to an array of text representations of the rows.
*
* @returns {Array<string>} An array of text representations of the rows.
*/
toLines() {
return this._rows.map(row => row.toText());
}
/**
* Computes a focus from a point in the text editor.
*
* @param {Point} pos - A point in the text editor.
* @param {number} rowOffset - The row index where the table starts in the text editor.
* @returns {Focus|undefined} A focus object that corresponds to the specified point;
* `undefined` if the row index is out of bounds.
*/
focusOfPosition(pos, rowOffset) {
const rowIndex = pos.row - rowOffset;
const row = this._rows[rowIndex];
if (row === undefined) {
return undefined;
}
if (pos.column < row.marginLeft.length + 1) {
return new Focus(rowIndex, -1, pos.column);
}
else {
const cellWidths = row.getCells().map(cell => cell.rawContent.length);
let columnPos = row.marginLeft.length + 1; // left margin + a pipe
let columnIndex = 0;
for (; columnIndex < cellWidths.length; columnIndex++) {
if (columnPos + cellWidths[columnIndex] + 1 > pos.column) {
break;
}
columnPos += cellWidths[columnIndex] + 1;
}
const offset = pos.column - columnPos;
return new Focus(rowIndex, columnIndex, offset);
}
}
/**
* Computes a position in the text editor from a focus.
*
* @param {Focus} focus - A focus object.
* @param {number} rowOffset - The row index where the table starts in the text editor.
* @returns {Point|undefined} A position in the text editor that corresponds to the focus;
* `undefined` if the focused row is out of the table.
*/
positionOfFocus(focus, rowOffset) {
const row = this._rows[focus.row];
if (row === undefined) {
return undefined;
}
const rowPos = focus.row + rowOffset;
if (focus.column < 0) {
return new Point(rowPos, focus.offset);
}
const cellWidths = row.getCells().map(cell => cell.rawContent.length);
const maxIndex = Math.min(focus.column, cellWidths.length);
let columnPos = row.marginLeft.length + 1;
for (let columnIndex = 0; columnIndex < maxIndex; columnIndex++) {
columnPos += cellWidths[columnIndex] + 1;
}
return new Point(rowPos, columnPos + focus.offset);
}
/**
* Computes a selection range from a focus.
*
* @param {Focus} focus - A focus object.
* @param {number} rowOffset - The row index where the table starts in the text editor.
* @returns {Range|undefined} A range to be selected that corresponds to the focus;
* `undefined` if the focus does not specify any cell or the specified cell is empty.
*/
selectionRangeOfFocus(focus, rowOffset) {
const row = this._rows[focus.row];
if (row === undefined) {
return undefined;
}
const cell = row.getCellAt(focus.column);
if (cell === undefined) {
return undefined;
}
if (cell.content === "") {
return undefined;
}
const rowPos = focus.row + rowOffset;
const cellWidths = row.getCells().map(cell => cell.rawContent.length);
let columnPos = row.marginLeft.length + 1;
for (let columnIndex = 0; columnIndex < focus.column; columnIndex++) {
columnPos += cellWidths[columnIndex] + 1;
}
columnPos += cell.paddingLeft;
return new Range(
new Point(rowPos, columnPos),
new Point(rowPos, columnPos + cell.content.length)
);
}
}
/**
* Splits a text into cells.
*
* @private
* @param {string} text
* @returns {Array<string>}
*/
function _splitCells(text) {
const cells = [];
let buf = "";
let rest = text;
while (rest !== "") {
switch (rest[0]) {
case "`":
// read code span
{
const start = rest.match(/^`*/)[0];
let buf1 = start;
let rest1 = rest.substr(start.length);
let closed = false;
while (rest1 !== "") {
if (rest1[0] === "`") {
const end = rest1.match(/^`*/)[0];
buf1 += end;
rest1 = rest1.substr(end.length);
if (end.length === start.length) {
closed = true;
break;
}
}
else {
buf1 += rest1[0];
rest1 = rest1.substr(1);
}
}
if (closed) {
buf += buf1;
rest = rest1;
}
else {
buf += "`";
rest = rest.substr(1);
}
}
break;
case "\\":
// escape next character
if (rest.length >= 2) {
buf += rest.substr(0, 2);
rest = rest.substr(2);
}
else {
buf += "\\";
rest = rest.substr(1);
}
break;
case "|":
// flush buffer
cells.push(buf);
buf = "";
rest = rest.substr(1);
break;
default:
buf += rest[0];
rest = rest.substr(1);
}
}
cells.push(buf);
return cells;
}
/**
* Reads a table row.
*
* @private
* @param {string} text - A text
* @returns {TableRow}
*/
function _readRow(text) {
let cells = _splitCells(text);
let marginLeft;
if (cells.length > 0 && /^\s*$/.test(cells[0])) {
marginLeft = cells[0];
cells = cells.slice(1);
}
else {
marginLeft = "";
}
let marginRight;
if (cells.length > 1 && /^\s*$/.test(cells[cells.length - 1])) {
marginRight = cells[cells.length - 1];
cells = cells.slice(0, cells.length - 1);
}
else {
marginRight = "";
}
return new TableRow(cells.map(cell => new TableCell(cell)), marginLeft, marginRight);
}
/**
* Reads a table from lines.
*
* @private
* @param {Array<string>} lines - An array of texts, each text represents a row.
* @returns {Table} The table red from the lines.
*/
function readTable(lines) {
return new Table(lines.map(_readRow));
}
/**
* Creates a delimiter text.
*
* @private
* @param {Alignment} alignment
* @param {number} width - Width of the horizontal bar of delimiter.
* @returns {string}
* @throws {Error} Unknown alignment.
*/
function _delimiterText(alignment, width) {
const bar = "-".repeat(width);
switch (alignment) {
case Alignment.NONE:
return ` ${bar} `;
case Alignment.LEFT:
return `:${bar} `;
case Alignment.RIGHT:
return ` ${bar}:`;
case Alignment.CENTER:
return `:${bar}:`;
default:
throw new Error("Unknown alignment: " + alignment);
}
}
/**
* Extends array size.
*
* @private
* @param {Array} arr
* @param {number} size
* @param {Function} callback - Callback function to fill newly created cells.
* @returns {Array} Extended array.
*/
function _extendArray(arr, size, callback) {
const extended = arr.slice();
for (let i = arr.length; i < size; i++) {
extended.push(callback(i, arr));
}
return extended;
}
/**
* Completes a table by adding missing delimiter and cells.
* After completion, all rows in the table have the same width.
*
* @private
* @param {Table} table - A table object.
* @param {Object} options - An object containing options for completion.
*
* | property name | type | description |
* | ------------------- | -------------- | --------------------------------------------------------- |
* | `minDelimiterWidth` | {@link number} | Width of delimiters used when completing delimiter cells. |
*
* @returns {Object} An object that represents the result of the completion.
*
* | property name | type | description |
* | ------------------- | --------------- | -------------------------------------- |
* | `table` | {@link Table} | A completed table object. |
* | `delimiterInserted` | {@link boolean} | `true` if a delimiter row is inserted. |
*
* @throws {Error} Empty table.
*/
function completeTable(table, options) {
const tableHeight = table.getHeight();
const tableWidth = table.getWidth();
if (tableHeight === 0) {
throw new Error("Empty table");
}
const rows = table.getRows();
const newRows = [];
// header
const headerRow = rows[0];
const headerCells = headerRow.getCells();
newRows.push(new TableRow(
_extendArray(headerCells, tableWidth, j => new TableCell(
j === headerCells.length ? headerRow.marginRight : ""
)),
headerRow.marginLeft,
headerCells.length < tableWidth ? "" : headerRow.marginRight
));
// delimiter
const delimiterRow = table.getDelimiterRow();
if (delimiterRow !== undefined) {
const delimiterCells = delimiterRow.getCells();
newRows.push(new TableRow(
_extendArray(delimiterCells, tableWidth, j => new TableCell(
_delimiterText(
Alignment.NONE,
j === delimiterCells.length
? Math.max(options.minDelimiterWidth, delimiterRow.marginRight.length - 2)
: options.minDelimiterWidth
)
)),
delimiterRow.marginLeft,
delimiterCells.length < tableWidth ? "" : delimiterRow.marginRight
));
}
else {
newRows.push(new TableRow(
_extendArray([], tableWidth, () => new TableCell(
_delimiterText(Alignment.NONE, options.minDelimiterWidth)
)),
"",
""
));
}
// body
for (let i = delimiterRow !== undefined ? 2 : 1; i < tableHeight; i++) {
const row = rows[i];
const cells = row.getCells();
newRows.push(new TableRow(
_extendArray(cells, tableWidth, j => new TableCell(
j === cells.length ? row.marginRight : ""
)),
row.marginLeft,
cells.length < tableWidth ? "" : row.marginRight
));
}
return {
table : new Table(newRows),
delimiterInserted: delimiterRow === undefined
};
}
/**
* Calculates the width of a text based on characters' EAW properties.
*
* @private
* @param {string} text
* @param {Object} options -
*
* | property name | type |
* | ----------------- | ---------------------------------- |
* | `normalize` | {@link boolean} |
* | `wideChars` | {@link Set}&lt;{@link string} &gt; |
* | `narrowChars` | {@link Set}&lt;{@link string} &gt; |
* | `ambiguousAsWide` | {@link boolean} |
*
* @returns {number} Calculated width of the text.
*/
function _computeTextWidth(text, options) {
const normalized = options.normalize ? text.normalize("NFC") : text;
let w = 0;
for (const char of normalized) {
if (options.wideChars.has(char)) {
w += 2;
continue;
}
if (options.narrowChars.has(char)) {
w += 1;
continue;
}
switch (Object(__WEBPACK_IMPORTED_MODULE_0_meaw__["a" /* getEAW */])(char)) {
case "F":
case "W":
w += 2;
break;
case "A":
w += options.ambiguousAsWide ? 2 : 1;
break;
default:
w += 1;
}
}
return w;
}
/**
* Returns a aligned cell content.
*
* @private
* @param {string} text
* @param {number} width
* @param {Alignment} alignment
* @param {Object} options - Options for computing text width.
* @returns {string}
* @throws {Error} Unknown alignment.
* @throws {Error} Unexpected default alignment.
*/
function _alignText(text, width, alignment, options) {
const space = width - _computeTextWidth(text, options);
if (space < 0) {
return text;
}
switch (alignment) {
case Alignment.NONE:
throw new Error("Unexpected default alignment");
case Alignment.LEFT:
return text + " ".repeat(space);
case Alignment.RIGHT:
return " ".repeat(space) + text;
case Alignment.CENTER:
return " ".repeat(Math.floor(space / 2))
+ text
+ " ".repeat(Math.ceil(space / 2));
default:
throw new Error("Unknown alignment: " + alignment);
}
}
/**
* Just adds one space paddings to both sides of a text.
*
* @private
* @param {string} text
* @returns {string}
*/
function _padText(text) {
return ` ${text} `;
}
/**
* Formats a table.
*
* @private
* @param {Table} table - A table object.
* @param {Object} options - An object containing options for formatting.
*
* | property name | type | description |
* | ------------------- | ------------------------ | ------------------------------------------------------- |
* | `minDelimiterWidth` | {@link number} | Minimum width of delimiters. |
* | `defaultAlignment` | {@link DefaultAlignment} | Default alignment of columns. |
* | `headerAlignment` | {@link HeaderAlignment} | Alignment of header cells. |
* | `textWidthOptions` | {@link Object} | An object containing options for computing text widths. |
*
* `options.textWidthOptions` must contain the following options.
*
* | property name | type | description |
* | ----------------- | --------------------------------- | --------------------------------------------------- |
* | `normalize` | {@link boolean} | Normalize texts before computing text widths. |
* | `wideChars` | {@link Set}&lt;{@link string}&gt; | Set of characters that should be treated as wide. |
* | `narrowChars` | {@link Set}&lt;{@link string}&gt; | Set of characters that should be treated as narrow. |
* | `ambiguousAsWide` | {@link boolean} | Treat East Asian Ambiguous characters as wide. |
*
* @returns {Object} An object that represents the result of formatting.
*
* | property name | type | description |
* | --------------- | -------------- | ---------------------------------------------- |
* | `table` | {@link Table} | A formatted table object. |
* | `marginLeft` | {@link string} | The common left margin of the formatted table. |
*/
function _formatTable(table, options) {
const tableHeight = table.getHeight();
const tableWidth = table.getWidth();
if (tableHeight === 0) {
return {
table,
marginLeft: ""
};
}
const marginLeft = table.getRowAt(0).marginLeft;
if (tableWidth === 0) {
const rows = new Array(tableHeight).fill()
.map(() => new TableRow([], marginLeft, ""));
return {
table: new Table(rows),
marginLeft
};
}
// compute column widths
const delimiterRow = table.getDelimiterRow();
const columnWidths = new Array(tableWidth).fill(0);
if (delimiterRow !== undefined) {
const delimiterRowWidth = delimiterRow.getWidth();
for (let j = 0; j < delimiterRowWidth; j++) {
columnWidths[j] = options.minDelimiterWidth;
}
}
for (let i = 0; i < tableHeight; i++) {
if (delimiterRow !== undefined && i === 1) {
continue;
}
const row = table.getRowAt(i);
const rowWidth = row.getWidth();
for (let j = 0; j < rowWidth; j++) {
columnWidths[j] = Math.max(
columnWidths[j],
_computeTextWidth(row.getCellAt(j).content, options.textWidthOptions)
);
}
}
// get column alignments
const alignments = delimiterRow !== undefined
? _extendArray(
delimiterRow.getCells().map(cell => cell.getAlignment()),
tableWidth,
() => options.defaultAlignment
)
: new Array(tableWidth).fill(options.defaultAlignment);
// format
const rows = [];
// header
const headerRow = table.getRowAt(0);
rows.push(new TableRow(
headerRow.getCells().map((cell, j) =>
new TableCell(_padText(_alignText(
cell.content,
columnWidths[j],
options.headerAlignment === HeaderAlignment.FOLLOW
? (alignments[j] === Alignment.NONE ? options.defaultAlignment : alignments[j])
: options.headerAlignment,
options.textWidthOptions
)))
),
marginLeft,
""
));
// delimiter
if (delimiterRow !== undefined) {
rows.push(new TableRow(
delimiterRow.getCells().map((cell, j) =>
new TableCell(_delimiterText(alignments[j], columnWidths[j]))
),
marginLeft,
""
));
}
// body
for (let i = delimiterRow !== undefined ? 2 : 1; i < tableHeight; i++) {
const row = table.getRowAt(i);
rows.push(new TableRow(
row.getCells().map((cell, j) =>
new TableCell(_padText(_alignText(
cell.content,
columnWidths[j],
alignments[j] === Alignment.NONE ? options.defaultAlignment : alignments[j],
options.textWidthOptions
)))
),
marginLeft,
""
));
}
return {
table: new Table(rows),
marginLeft
};
}
/**
* Formats a table weakly.
* Rows are formatted independently to each other, cell contents are just trimmed and not aligned.
* This is useful when using a non-monospaced font or dealing with wide tables.
*
* @private
* @param {Table} table - A table object.
* @param {Object} options - An object containing options for formatting.
* The function accepts the same option object for {@link formatTable}, but properties not listed
* here are just ignored.
*
* | property name | type | description |
* | ------------------- | -------------- | -------------------- |
* | `minDelimiterWidth` | {@link number} | Width of delimiters. |
*
* @returns {Object} An object that represents the result of formatting.
*
* | property name | type | description |
* | --------------- | -------------- | ---------------------------------------------- |
* | `table` | {@link Table} | A formatted table object. |
* | `marginLeft` | {@link string} | The common left margin of the formatted table. |
*/
function _weakFormatTable(table, options) {
const tableHeight = table.getHeight();
const tableWidth = table.getWidth();
if (tableHeight === 0) {
return {
table,
marginLeft: ""
};
}
const marginLeft = table.getRowAt(0).marginLeft;
if (tableWidth === 0) {
const rows = new Array(tableHeight).fill()
.map(() => new TableRow([], marginLeft, ""));
return {
table: new Table(rows),
marginLeft
};
}
const delimiterRow = table.getDelimiterRow();
// format
const rows = [];
// header
const headerRow = table.getRowAt(0);
rows.push(new TableRow(
headerRow.getCells().map(cell =>
new TableCell(_padText(cell.content))
),
marginLeft,
""
));
// delimiter
if (delimiterRow !== undefined) {
rows.push(new TableRow(
delimiterRow.getCells().map(cell =>
new TableCell(_delimiterText(cell.getAlignment(), options.minDelimiterWidth))
),
marginLeft,
""
));
}
// body
for (let i = delimiterRow !== undefined ? 2 : 1; i < tableHeight; i++) {
const row = table.getRowAt(i);
rows.push(new TableRow(
row.getCells().map(cell =>
new TableCell(_padText(cell.content))
),
marginLeft,
""
));
}
return {
table: new Table(rows),
marginLeft
};
}
/**
* Represents table format type.
*
* - `FormatType.NORMAL` - Formats table normally.
* - `FormatType.WEAK` - Formats table weakly, rows are formatted independently to each other, cell
* contents are just trimmed and not aligned.
*
* @type {Object}
*/
const FormatType = Object.freeze({
NORMAL: "normal",
WEAK : "weak"
});
/**
* Formats a table.
*
* @private
* @param {Table} table - A table object.
* @param {Object} options - An object containing options for formatting.
*
* | property name | type | description |
* | ------------------- | ------------------------ | ------------------------------------------------------- |
* | `formatType` | {@link FormatType} | Format type, normal or weak. |
* | `minDelimiterWidth` | {@link number} | Minimum width of delimiters. |
* | `defaultAlignment` | {@link DefaultAlignment} | Default alignment of columns. |
* | `headerAlignment` | {@link HeaderAlignment} | Alignment of header cells. |
* | `textWidthOptions` | {@link Object} | An object containing options for computing text widths. |
*
* `options.textWidthOptions` must contain the following options.
*
* | property name | type | description |
* | ----------------- | --------------------------------- | --------------------------------------------------- |
* | `normalize` | {@link boolean} | Normalize texts before computing text widths. |
* | `wideChars` | {@link Set}&lt;{@link string}&gt; | Set of characters that should be treated as wide. |
* | `narrowChars` | {@link Set}&lt;{@link string}&gt; | Set of characters that should be treated as narrow. |
* | `ambiguousAsWide` | {@link boolean} | Treat East Asian Ambiguous characters as wide. |
*
* @returns {Object} An object that represents the result of formatting.
*
* | property name | type | description |
* | --------------- | -------------- | ---------------------------------------------- |
* | `table` | {@link Table} | A formatted table object. |
* | `marginLeft` | {@link string} | The common left margin of the formatted table. |
*
* @throws {Error} Unknown format type.
*/
function formatTable(table, options) {
switch (options.formatType) {
case FormatType.NORMAL:
return _formatTable(table, options);
case FormatType.WEAK:
return _weakFormatTable(table, options);
default:
throw new Error("Unknown format type: " + options.formatType);
}
}
/**
* Alters a column's alignment of a table.
*
* @private
* @param {Table} table - A completed non-empty table.
* @param {number} columnIndex - An index of the column.
* @param {Alignment} alignment - A new alignment of the column.
* @param {Object} options - An object containing options for completion.
*
* | property name | type | description |
* | ------------------- | -------------- | -------------------- |
* | `minDelimiterWidth` | {@link number} | Width of delimiters. |
*
* @returns {Table} An altered table object.
* If the column index is out of range, returns the original table.
*/
function alterAlignment(table, columnIndex, alignment, options) {
const delimiterRow = table.getRowAt(1);
if (columnIndex < 0 || delimiterRow.getWidth() - 1 < columnIndex) {
return table;
}
const delimiterCells = delimiterRow.getCells();
delimiterCells[columnIndex] = new TableCell(_delimiterText(alignment, options.minDelimiterWidth));
const rows = table.getRows();
rows[1] = new TableRow(delimiterCells, delimiterRow.marginLeft, delimiterRow.marginRight);
return new Table(rows);
}
/**
* Inserts a row to a table.
* The row is always inserted after the header and the delimiter rows, even if the index specifies
* the header or the delimiter.
*
* @private
* @param {Table} table - A completed non-empty table.
* @param {number} rowIndex - An row index at which a new row will be inserted.
* @param {TableRow} row - A table row to be inserted.
* @returns {Table} An altered table obejct.
*/
function insertRow(table, rowIndex, row) {
const rows = table.getRows();
rows.splice(Math.max(rowIndex, 2), 0, row);
return new Table(rows);
}
/**
* Deletes a row in a table.
* If the index specifies the header row, the cells are emptied but the row will not be removed.
* If the index specifies the delimiter row, it does nothing.
*
* @private
* @param {Table} table - A completed non-empty table.
* @param {number} rowIndex - An index of the row to be deleted.
* @returns {Table} An altered table obejct.
*/
function deleteRow(table, rowIndex) {
if (rowIndex === 1) {
return table;
}
const rows = table.getRows();
if (rowIndex === 0) {
const headerRow = rows[0];
rows[0] = new TableRow(
new Array(headerRow.getWidth()).fill(new TableCell("")),
headerRow.marginLeft,
headerRow.marginRight
);
}
else {
rows.splice(rowIndex, 1);
}
return new Table(rows);
}
/**
* Moves a row at the index to the specified destination.
*
* @private
* @param {Table} table - A completed non-empty table.
* @param {number} rowIndex - Index of the row to be moved.
* @param {number} destIndex - Index of the destination.
* @returns {Table} An altered table object.
*/
function moveRow(table, rowIndex, destIndex) {
if (rowIndex <= 1 || destIndex <= 1 || rowIndex === destIndex) {
return table;
}
const rows = table.getRows();
const row = rows[rowIndex];
rows.splice(rowIndex, 1);
rows.splice(destIndex, 0, row);
return new Table(rows);
}
/**
* Inserts a column to a table.
*
* @private
* @param {Table} table - A completed non-empty table.
* @param {number} columnIndex - An column index at which the new column will be inserted.
* @param {Array<TableCell>} column - An array of cells.
* @param {Object} options - An object containing options for completion.
*
* | property name | type | description |
* | ------------------- | -------------- | ----------------------- |
* | `minDelimiterWidth` | {@link number} | Width of the delimiter. |
*
* @returns {Table} An altered table obejct.
*/
function insertColumn(table, columnIndex, column, options) {
const rows = table.getRows();
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const cells = rows[i].getCells();
const cell = i === 1
? new TableCell(_delimiterText(Alignment.NONE, options.minDelimiterWidth))
: column[i > 1 ? i - 1 : i];
cells.splice(columnIndex, 0, cell);
rows[i] = new TableRow(cells, row.marginLeft, row.marginRight);
}
return new Table(rows);
}
/**
* Deletes a column in a table.
* If there will be no columns after the deletion, the cells are emptied but the column will not be
* removed.
*
* @private
* @param {Table} table - A completed non-empty table.
* @param {number} columnIndex - An index of the column to be deleted.
* @param {Object} options - An object containing options for completion.
*
* | property name | type | description |
* | ------------------- | -------------- | ----------------------- |
* | `minDelimiterWidth` | {@link number} | Width of the delimiter. |
*
* @returns {Table} An altered table object.
*/
function deleteColumn(table, columnIndex, options) {
const rows = table.getRows();
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
let cells = row.getCells();
if (cells.length <= 1) {
cells = [new TableCell(i === 1
? _delimiterText(Alignment.NONE, options.minDelimiterWidth)
: ""
)];
}
else {
cells.splice(columnIndex, 1);
}
rows[i] = new TableRow(cells, row.marginLeft, row.marginRight);
}
return new Table(rows);
}
/**
* Moves a column at the index to the specified destination.
*
* @private
* @param {Table} table - A completed non-empty table.
* @param {number} columnIndex - Index of the column to be moved.
* @param {number} destIndex - Index of the destination.
* @returns {Table} An altered table object.
*/
function moveColumn(table, columnIndex, destIndex) {
if (columnIndex === destIndex) {
return table;
}
const rows = table.getRows();
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
const cells = row.getCells();
const cell = cells[columnIndex];
cells.splice(columnIndex, 1);
cells.splice(destIndex, 0, cell);
rows[i] = new TableRow(cells, row.marginLeft, row.marginRight);
}
return new Table(rows);
}
/**
* The `Insert` class represents an insertion of a line.
*
* @private
*/
class Insert {
/**
* Creats a new `Insert` object.
*
* @param {number} row - Row index, starts from `0`.
* @param {string} line - A string to be inserted at the row.
*/
constructor(row, line) {
/** @private */
this._row = row;
/** @private */
this._line = line;
}
/**
* Row index, starts from `0`.
*
* @type {number}
*/
get row() {
return this._row;
}
/**
* A string to be inserted.
*
* @type {string}
*/
get line() {
return this._line;
}
}
/**
* The `Delete` class represents a deletion of a line.
*
* @private
*/
class Delete {
/**
* Creates a new `Delete` object.
*
* @param {number} row - Row index, starts from `0`.
*/
constructor(row) {
/** @private */
this._row = row;
}
/**
* Row index, starts from `0`.
*
* @type {number}
*/
get row() {
return this._row;
}
}
/**
* Applies a command to the text editor.
*
* @private
* @param {ITextEditor} textEditor - An interface to the text editor.
* @param {Insert|Delete} command - A command.
* @param {number} rowOffset - Offset to the row index of the command.
* @returns {undefined}
*/
function _applyCommand(textEditor, command, rowOffset) {
if (command instanceof Insert) {
textEditor.insertLine(rowOffset + command.row, command.line);
}
else if (command instanceof Delete) {
textEditor.deleteLine(rowOffset + command.row);
}
else {
throw new Error("Unknown command");
}
}
/**
* Apply an edit script (array of commands) to the text editor.
*
* @private
* @param {ITextEditor} textEditor - An interface to the text editor.
* @param {Array<Insert|Delete>} script - An array of commands.
* The commands are applied sequentially in the order of the array.
* @param {number} rowOffset - Offset to the row index of the commands.
* @returns {undefined}
*/
function applyEditScript(textEditor, script, rowOffset) {
for (const command of script) {
_applyCommand(textEditor, command, rowOffset);
}
}
/**
* Linked list used to remember edit script.
*
* @private
*/
class IList {
get car() {
throw new Error("Not implemented");
}
get cdr() {
throw new Error("Not implemented");
}
isEmpty() {
throw new Error("Not implemented");
}
unshift(value) {
return new Cons(value, this);
}
toArray() {
const arr = [];
let rest = this;
while (!rest.isEmpty()) {
arr.push(rest.car);
rest = rest.cdr;
}
return arr;
}
}
/**
* @private
*/
class Nil extends IList {
constructor() {
super();
}
get car() {
throw new Error("Empty list");
}
get cdr() {
throw new Error("Empty list");
}
isEmpty() {
return true;
}
}
/**
* @private
*/
class Cons extends IList {
constructor(car, cdr) {
super();
this._car = car;
this._cdr = cdr;
}
get car() {
return this._car;
}
get cdr() {
return this._cdr;
}
isEmpty() {
return false;
}
}
const nil = new Nil();
/**
* Computes the shortest edit script between two arrays of strings.
*
* @private
* @param {Array<string>} from - An array of string the edit starts from.
* @param {Array<string>} to - An array of string the edit goes to.
* @param {number} [limit=-1] - Upper limit of edit distance to be searched.
* If negative, there is no limit.
* @returns {Array<Insert|Delete>|undefined} The shortest edit script that turns `from` into `to`;
* `undefined` if no edit script is found in the given range.
*/
function shortestEditScript(from, to, limit = -1) {
const fromLen = from.length;
const toLen = to.length;
const maxd = limit >= 0 ? Math.min(limit, fromLen + toLen) : fromLen + toLen;
const mem = new Array(Math.min(maxd, fromLen) + Math.min(maxd, toLen) + 1);
const offset = Math.min(maxd, fromLen);
for (let d = 0; d <= maxd; d++) {
const mink = d <= fromLen ? -d : d - 2 * fromLen;
const maxk = d <= toLen ? d : -d + 2 * toLen;
for (let k = mink; k <= maxk; k += 2) {
let i;
let script;
if (d === 0) {
i = 0;
script = nil;
}
else if (k === -d) {
i = mem[offset + k + 1].i + 1;
script = mem[offset + k + 1].script.unshift(new Delete(i + k));
}
else if (k === d) {
i = mem[offset + k - 1].i;
script = mem[offset + k - 1].script.unshift(new Insert(i + k - 1, to[i + k - 1]));
}
else {
const vi = mem[offset + k + 1].i + 1;
const hi = mem[offset + k - 1].i;
if (vi > hi) {
i = vi;
script = mem[offset + k + 1].script.unshift(new Delete(i + k));
}
else {
i = hi;
script = mem[offset + k - 1].script.unshift(new Insert(i + k - 1, to[i + k - 1]));
}
}
while (i < fromLen && i + k < toLen && from[i] === to[i + k]) {
i += 1;
}
if (k === toLen - fromLen && i === fromLen) {
return script.toArray().reverse();
}
mem[offset + k] = { i, script };
}
}
return undefined;
}
/**
* The `ITextEditor` represents an interface to a text editor.
*
* @interface
*/
class ITextEditor {
/**
* Gets the current cursor position.
*
* @returns {Point} A point object that represents the cursor position.
*/
getCursorPosition() {
throw new Error("Not implemented: getCursorPosition");
}
/**
* Sets the cursor position to a specified one.
*
* @param {Point} pos - A point object which the cursor position is set to.
* @returns {undefined}
*/
setCursorPosition(pos) {
throw new Error("Not implemented: setCursorPosition");
}
/**
* Sets the selection range.
* This method also expects the cursor position to be moved as the end of the selection range.
*
* @param {Range} range - A range object that describes a selection range.
* @returns {undefined}
*/
setSelectionRange(range) {
throw new Error("Not implemented: setSelectionRange");
}
/**
* Gets the last row index of the text editor.
*
* @returns {number} The last row index.
*/
getLastRow() {
throw new Error("Not implemented: getLastRow");
}
/**
* Checks if the editor accepts a table at a row to be editted.
* It should return `false` if, for example, the row is in a code block (not Markdown).
*
* @param {number} row - A row index in the text editor.
* @returns {boolean} `true` if the table at the row can be editted.
*/
acceptsTableEdit(row) {
throw new Error("Not implemented: acceptsTableEdit");
}
/**
* Gets a line string at a row.
*
* @param {number} row - Row index, starts from `0`.
* @returns {string} The line at the specified row.
* The line must not contain an EOL like `"\n"` or `"\r"`.
*/
getLine(row) {
throw new Error("Not implemented: getLine");
}
/**
* Inserts a line at a specified row.
*
* @param {number} row - Row index, starts from `0`.
* @param {string} line - A string to be inserted.
* This must not contain an EOL like `"\n"` or `"\r"`.
* @return {undefined}
*/
insertLine(row, line) {
throw new Error("Not implemented: insertLine");
}
/**
* Deletes a line at a specified row.
*
* @param {number} row - Row index, starts from `0`.
* @returns {undefined}
*/
deleteLine(row) {
throw new Error("Not implemented: deleteLine");
}
/**
* Replace lines in a specified range.
*
* @param {number} startRow - Start row index, starts from `0`.
* @param {number} endRow - End row index.
* Lines from `startRow` to `endRow - 1` is replaced.
* @param {Array<string>} lines - An array of string.
* Each strings must not contain an EOL like `"\n"` or `"\r"`.
* @returns {undefined}
*/
replaceLines(startRow, endRow, lines) {
throw new Error("Not implemented: replaceLines");
}
/**
* Batches multiple operations as a single undo/redo step.
*
* @param {Function} func - A callback function that executes some operations on the text editor.
* @returns {undefined}
*/
transact(func) {
throw new Error("Not implemented: transact");
}
}
/**
* Reads a property of an object if exists; otherwise uses a default value.
*
* @private
* @param {*} obj - An object. If a non-object value is specified, the default value is used.
* @param {string} key - A key (or property name).
* @param {*} defaultVal - A default value that is used when a value does not exist.
* @returns {*} A read value or the default value.
*/
function _value(obj, key, defaultVal) {
return (typeof obj === "object" && obj !== null && obj[key] !== undefined)
? obj[key]
: defaultVal;
}
/**
* Reads multiple properties of an object if exists; otherwise uses default values.
*
* @private
* @param {*} obj - An object. If a non-object value is specified, the default value is used.
* @param {Object} keys - An object that consists of pairs of a key and a default value.
* @returns {Object} A new object that contains read values.
*/
function _values(obj, keys) {
const res = {};
for (const [key, defaultVal] of Object.entries(keys)) {
res[key] = _value(obj, key, defaultVal);
}
return res;
}
/**
* Reads options for the formatter from an object.
* The default values are used for options that are not specified.
*
* @param {Object} obj - An object containing options.
* The available options and default values are listed below.
*
* | property name | type | description | default value |
* | ------------------- | ------------------------ | ------------------------------------------------------- | ------------------------ |
* | `formatType` | {@link FormatType} | Format type, normal or weak. | `FormatType.NORMAL` |
* | `minDelimiterWidth` | {@link number} | Minimum width of delimiters. | `3` |
* | `defaultAlignment` | {@link DefaultAlignment} | Default alignment of columns. | `DefaultAlignment.LEFT` |
* | `headerAlignment` | {@link HeaderAlignment} | Alignment of header cells. | `HeaderAlignment.FOLLOW` |
* | `textWidthOptions` | {@link Object} | An object containing options for computing text widths. | |
* | `smartCursor` | {@link boolean} | Enables "Smart Cursor" feature. | `false` |
*
* The available options for `obj.textWidthOptions` are the following ones.
*
* | property name | type | description | default value |
* | ----------------- | --------------------------------- | ----------------------------------------------------- | ------------- |
* | `normalize` | {@link boolean} | Normalizes texts before computing text widths. | `true` |
* | `wideChars` | {@link Set}&lt;{@link string}&gt; | A set of characters that should be treated as wide. | `new Set()` |
* | `narrowChars` | {@link Set}&lt;{@link string}&gt; | A set of characters that should be treated as narrow. | `new Set()` |
* | `ambiguousAsWide` | {@link boolean} | Treats East Asian Ambiguous characters as wide. | `false` |
*
* @returns {Object} - An object that contains complete options.
*/
function options(obj) {
const res = _values(obj, {
formatType : FormatType.NORMAL,
minDelimiterWidth: 3,
defaultAlignment : DefaultAlignment.LEFT,
headerAlignment : HeaderAlignment.FOLLOW,
smartCursor : false
});
res.textWidthOptions = _values(obj.textWidthOptions, {
normalize : true,
wideChars : new Set(),
narrowChars : new Set(),
ambiguousAsWide: false
});
return res;
}
/**
* Checks if a line is a table row.
*
* @private
* @param {string} line - A string.
* @returns {boolean} `true` if the given line starts with a pipe `|`.
*/
function _isTableRow(line) {
return line.trimLeft()[0] === "|";
}
/**
* Computes new focus offset from information of completed and formatted tables.
*
* @private
* @param {Focus} focus - A focus.
* @param {Table} table - A completed but not formatted table with original cell contents.
* @param {Object} formatted - Information of the formatted table.
* @param {boolean} moved - Indicates whether the focus position is moved by a command or not.
* @returns {number}
*/
function _computeNewOffset(focus, table, formatted, moved) {
if (moved) {
const formattedFocusedCell = formatted.table.getFocusedCell(focus);
if (formattedFocusedCell !== undefined) {
return formattedFocusedCell.computeRawOffset(0);
}
else {
return focus.column < 0 ? formatted.marginLeft.length : 0;
}
}
else {
const focusedCell = table.getFocusedCell(focus);
const formattedFocusedCell = formatted.table.getFocusedCell(focus);
if (focusedCell !== undefined && formattedFocusedCell !== undefined) {
const contentOffset = Math.min(
focusedCell.computeContentOffset(focus.offset),
formattedFocusedCell.content.length
);
return formattedFocusedCell.computeRawOffset(contentOffset);
}
else {
return focus.column < 0 ? formatted.marginLeft.length : 0;
}
}
}
/**
* The `TableEditor` class is at the center of the markdown-table-editor.
* When a command is executed, it reads a table from the text editor, does some operation on the
* table, and then apply the result to the text editor.
*
* To use this class, the text editor (or an interface to it) must implement {@link ITextEditor}.
*/
class TableEditor {
/**
* Creates a new table editor instance.
*
* @param {ITextEditor} textEditor - A text editor interface.
*/
constructor(textEditor) {
/** @private */
this._textEditor = textEditor;
// smart cursor
/** @private */
this._scActive = false;
/** @private */
this._scTablePos = null;
/** @private */
this._scStartFocus = null;
/** @private */
this._scLastFocus = null;
}
/**
* Resets the smart cursor.
* Call this method when the table editor is inactivated.
*
* @returns {undefined}
*/
resetSmartCursor() {
this._scActive = false;
}
/**
* Checks if the cursor is in a table row.
* This is useful to check whether the table editor should be activated or not.
*
* @returns {boolean} `true` if the cursor is in a table row.
*/
cursorIsInTable() {
const pos = this._textEditor.getCursorPosition();
return this._textEditor.acceptsTableEdit(pos.row)
&& _isTableRow(this._textEditor.getLine(pos.row));
}
/**
* Finds a table under the current cursor position.
*
* @private
* @returns {Object|undefined} An object that contains information about the table;
* `undefined` if there is no table.
* The return object contains the properties listed in the table.
*
* | property name | type | description |
* | --------------- | ----------------------------------- | ------------------------------------------------------------------------ |
* | `range` | {@link Range} | The range of the table. |
* | `lines` | {@link Array}&lt;{@link string}&gt; | An array of the lines in the range. |
* | `table` | {@link Table} | A table object read from the text editor. |
* | `focus` | {@link Focus} | A focus object that represents the current cursor position in the table. |
*/
_findTable() {
const pos = this._textEditor.getCursorPosition();
const lastRow = this._textEditor.getLastRow();
const lines = [];
let startRow = pos.row;
let endRow = pos.row;
// current line
{
const line = this._textEditor.getLine(pos.row);
if (!this._textEditor.acceptsTableEdit(pos.row) || !_isTableRow(line)) {
return undefined;
}
lines.push(line);
}
// previous lines
for (let row = pos.row - 1; row >= 0; row--) {
const line = this._textEditor.getLine(row);
if (!this._textEditor.acceptsTableEdit(row) || !_isTableRow(line)) {
break;
}
lines.unshift(line);
startRow = row;
}
// next lines
for (let row = pos.row + 1; row <= lastRow; row++) {
const line = this._textEditor.getLine(row);
if (!this._textEditor.acceptsTableEdit(row) || !_isTableRow(line)) {
break;
}
lines.push(line);
endRow = row;
}
const range = new Range(
new Point(startRow, 0),
new Point(endRow, lines[lines.length - 1].length)
);
const table = readTable(lines);
const focus = table.focusOfPosition(pos, startRow);
return { range, lines, table, focus };
}
/**
* Finds a table and does an operation with it.
*
* @private
* @param {Function} func - A function that does some operation on table information obtained by
* {@link TableEditor#_findTable}.
* @returns {undefined}
*/
_withTable(func) {
const info = this._findTable();
if (info === undefined) {
return;
}
func(info);
}
/**
* Updates lines in a given range in the text editor.
*
* @private
* @param {number} startRow - Start row index, starts from `0`.
* @param {number} endRow - End row index.
* Lines from `startRow` to `endRow - 1` are replaced.
* @param {Array<string>} newLines - New lines.
* @param {Array<string>} [oldLines=undefined] - Old lines to be replaced.
* @returns {undefined}
*/
_updateLines(startRow, endRow, newLines, oldLines = undefined) {
if (oldLines !== undefined) {
// apply the shortest edit script
// if a table is edited in a normal manner, the edit distance never exceeds 3
const ses = shortestEditScript(oldLines, newLines, 3);
if (ses !== undefined) {
applyEditScript(this._textEditor, ses, startRow);
return;
}
}
this._textEditor.replaceLines(startRow, endRow, newLines);
}
/**
* Moves the cursor position to the focused cell,
*
* @private
* @param {number} startRow - Row index where the table starts in the text editor.
* @param {Table} table - A table.
* @param {Focus} focus - A focus to which the cursor will be moved.
* @returns {undefined}
*/
_moveToFocus(startRow, table, focus) {
const pos = table.positionOfFocus(focus, startRow);
if (pos !== undefined) {
this._textEditor.setCursorPosition(pos);
}
}
/**
* Selects the focused cell.
* If the cell has no content to be selected, then just moves the cursor position.
*
* @private
* @param {number} startRow - Row index where the table starts in the text editor.
* @param {Table} table - A table.
* @param {Focus} focus - A focus to be selected.
* @returns {undefined}
*/
_selectFocus(startRow, table, focus) {
const range = table.selectionRangeOfFocus(focus, startRow);
if (range !== undefined) {
this._textEditor.setSelectionRange(range);
}
else {
this._moveToFocus(startRow, table, focus);
}
}
/**
* Formats the table under the cursor.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
format(options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
// format
const formatted = formatTable(completed.table, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, completed.table, formatted, false));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
this._moveToFocus(range.start.row, formatted.table, newFocus);
});
});
}
/**
* Formats and escapes from the table.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
escape(options) {
this._withTable(({ range, lines, table, focus }) => {
// complete
const completed = completeTable(table, options);
// format
const formatted = formatTable(completed.table, options);
// apply
const newPos = new Point(range.end.row + (completed.delimiterInserted ? 2 : 1), 0);
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
if (newPos.row > this._textEditor.getLastRow()) {
this._textEditor.insertLine(newPos.row, "");
}
this._textEditor.setCursorPosition(newPos);
});
this.resetSmartCursor();
});
}
/**
* Alters the alignment of the focused column.
*
* @param {Alignment} alignment - New alignment.
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
alignColumn(alignment, options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
// alter alignment
let altered = completed.table;
if (0 <= newFocus.column && newFocus.column <= altered.getHeaderWidth() - 1) {
altered = alterAlignment(completed.table, newFocus.column, alignment, options);
}
// format
const formatted = formatTable(altered, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, completed.table, formatted, false));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
this._moveToFocus(range.start.row, formatted.table, newFocus);
});
});
}
/**
* Selects the focused cell content.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
selectCell(options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
// format
const formatted = formatTable(completed.table, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, completed.table, formatted, false));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
this._selectFocus(range.start.row, formatted.table, newFocus);
});
});
}
/**
* Moves the focus to another cell.
*
* @param {number} rowOffset - Offset in row.
* @param {number} columnOffset - Offset in column.
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
moveFocus(rowOffset, columnOffset, options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
const startFocus = newFocus;
// move focus
if (rowOffset !== 0) {
const height = completed.table.getHeight();
// skip delimiter row
const skip =
newFocus.row < 1 && newFocus.row + rowOffset >= 1 ? 1
: newFocus.row > 1 && newFocus.row + rowOffset <= 1 ? -1
: 0;
newFocus = newFocus.setRow(
Math.min(Math.max(newFocus.row + rowOffset + skip, 0), height <= 2 ? 0 : height - 1)
);
}
if (columnOffset !== 0) {
const width = completed.table.getHeaderWidth();
if (!(newFocus.column < 0 && columnOffset < 0)
&& !(newFocus.column > width - 1 && columnOffset > 0)) {
newFocus = newFocus.setColumn(
Math.min(Math.max(newFocus.column + columnOffset, 0), width - 1)
);
}
}
const moved = !newFocus.posEquals(startFocus);
// format
const formatted = formatTable(completed.table, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, completed.table, formatted, moved));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
if (moved) {
this._selectFocus(range.start.row, formatted.table, newFocus);
}
else {
this._moveToFocus(range.start.row, formatted.table, newFocus);
}
});
if (moved) {
this.resetSmartCursor();
}
});
}
/**
* Moves the focus to the next cell.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
nextCell(options) {
this._withTable(({ range, lines, table, focus }) => {
// reset smart cursor if moved
const focusMoved = (this._scTablePos !== null && !range.start.equals(this._scTablePos))
|| (this._scLastFocus !== null && !focus.posEquals(this._scLastFocus));
if (this._scActive && focusMoved) {
this.resetSmartCursor();
}
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
const startFocus = newFocus;
let altered = completed.table;
// move focus
if (newFocus.row === 1) {
// move to next row
newFocus = newFocus.setRow(2);
if (options.smartCursor) {
if (newFocus.column < 0 || altered.getHeaderWidth() - 1 < newFocus.column) {
newFocus = newFocus.setColumn(0);
}
}
else {
newFocus = newFocus.setColumn(0);
}
// insert an empty row if needed
if (newFocus.row > altered.getHeight() - 1) {
const row = new Array(altered.getHeaderWidth()).fill(new TableCell(""));
altered = insertRow(altered, altered.getHeight(), new TableRow(row, "", ""));
}
}
else {
// insert an empty column if needed
if (newFocus.column > altered.getHeaderWidth() - 1) {
const column = new Array(altered.getHeight() - 1).fill(new TableCell(""));
altered = insertColumn(altered, altered.getHeaderWidth(), column, options);
}
// move to next column
newFocus = newFocus.setColumn(newFocus.column + 1);
}
// format
const formatted = formatTable(altered, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, altered, formatted, true));
// apply
const newLines = formatted.table.toLines();
if (newFocus.column > formatted.table.getHeaderWidth() - 1) {
// add margin
newLines[newFocus.row] += " ";
newFocus = newFocus.setOffset(1);
}
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, newLines, lines);
this._selectFocus(range.start.row, formatted.table, newFocus);
});
if (options.smartCursor) {
if (!this._scActive) {
// activate smart cursor
this._scActive = true;
this._scTablePos = range.start;
if (startFocus.column < 0 || formatted.table.getHeaderWidth() - 1 < startFocus.column) {
this._scStartFocus = new Focus(startFocus.row, 0, 0);
}
else {
this._scStartFocus = startFocus;
}
}
this._scLastFocus = newFocus;
}
});
}
/**
* Moves the focus to the previous cell.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
previousCell(options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
const startFocus = newFocus;
// move focus
if (newFocus.row === 0) {
if (newFocus.column > 0) {
newFocus = newFocus.setColumn(newFocus.column - 1);
}
}
else if (newFocus.row === 1) {
newFocus = new Focus(0, completed.table.getHeaderWidth() - 1, newFocus.offset);
}
else {
if (newFocus.column > 0) {
newFocus = newFocus.setColumn(newFocus.column - 1);
}
else {
newFocus = new Focus(
newFocus.row === 2 ? 0 : newFocus.row - 1,
completed.table.getHeaderWidth() - 1,
newFocus.offset
);
}
}
const moved = !newFocus.posEquals(startFocus);
// format
const formatted = formatTable(completed.table, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, completed.table, formatted, moved));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
if (moved) {
this._selectFocus(range.start.row, formatted.table, newFocus);
}
else {
this._moveToFocus(range.start.row, formatted.table, newFocus);
}
});
if (moved) {
this.resetSmartCursor();
}
});
}
/**
* Moves the focus to the next row.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
nextRow(options) {
this._withTable(({ range, lines, table, focus }) => {
// reset smart cursor if moved
const focusMoved = (this._scTablePos !== null && !range.start.equals(this._scTablePos))
|| (this._scLastFocus !== null && !focus.posEquals(this._scLastFocus));
if (this._scActive && focusMoved) {
this.resetSmartCursor();
}
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
const startFocus = newFocus;
let altered = completed.table;
// move focus
if (newFocus.row === 0) {
newFocus = newFocus.setRow(2);
}
else {
newFocus = newFocus.setRow(newFocus.row + 1);
}
if (options.smartCursor) {
if (this._scActive) {
newFocus = newFocus.setColumn(this._scStartFocus.column);
}
else if (newFocus.column < 0 || altered.getHeaderWidth() - 1 < newFocus.column) {
newFocus = newFocus.setColumn(0);
}
}
else {
newFocus = newFocus.setColumn(0);
}
// insert empty row if needed
if (newFocus.row > altered.getHeight() - 1) {
const row = new Array(altered.getHeaderWidth()).fill(new TableCell(""));
altered = insertRow(altered, altered.getHeight(), new TableRow(row, "", ""));
}
// format
const formatted = formatTable(altered, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, altered, formatted, true));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
this._selectFocus(range.start.row, formatted.table, newFocus);
});
if (options.smartCursor) {
if (!this._scActive) {
// activate smart cursor
this._scActive = true;
this._scTablePos = range.start;
if (startFocus.column < 0 || formatted.table.getHeaderWidth() - 1 < startFocus.column) {
this._scStartFocus = new Focus(startFocus.row, 0, 0);
}
else {
this._scStartFocus = startFocus;
}
}
this._scLastFocus = newFocus;
}
});
}
/**
* Inserts an empty row at the current focus.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
insertRow(options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
// move focus
if (newFocus.row <= 1) {
newFocus = newFocus.setRow(2);
}
newFocus = newFocus.setColumn(0);
// insert an empty row
const row = new Array(completed.table.getHeaderWidth()).fill(new TableCell(""));
const altered = insertRow(completed.table, newFocus.row, new TableRow(row, "", ""));
// format
const formatted = formatTable(altered, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, altered, formatted, true));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
this._moveToFocus(range.start.row, formatted.table, newFocus);
});
this.resetSmartCursor();
});
}
/**
* Deletes a row at the current focus.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
deleteRow(options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
// delete a row
let altered = completed.table;
let moved = false;
if (newFocus.row !== 1) {
altered = deleteRow(altered, newFocus.row);
moved = true;
if (newFocus.row > altered.getHeight() - 1) {
newFocus = newFocus.setRow(newFocus.row === 2 ? 0 : newFocus.row - 1);
}
}
// format
const formatted = formatTable(altered, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, altered, formatted, moved));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
if (moved) {
this._selectFocus(range.start.row, formatted.table, newFocus);
}
else {
this._moveToFocus(range.start.row, formatted.table, newFocus);
}
});
this.resetSmartCursor();
});
}
/**
* Moves the focused row by the specified offset.
*
* @param {number} offset - An offset the row is moved by.
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
moveRow(offset, options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
// move row
let altered = completed.table;
if (newFocus.row > 1) {
const dest = Math.min(Math.max(newFocus.row + offset, 2), altered.getHeight() - 1);
altered = moveRow(altered, newFocus.row, dest);
newFocus = newFocus.setRow(dest);
}
// format
const formatted = formatTable(altered, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, altered, formatted, false));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
this._moveToFocus(range.start.row, formatted.table, newFocus);
});
this.resetSmartCursor();
});
}
/**
* Inserts an empty column at the current focus.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
insertColumn(options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
// move focus
if (newFocus.row === 1) {
newFocus = newFocus.setRow(0);
}
if (newFocus.column < 0) {
newFocus = newFocus.setColumn(0);
}
// insert an empty column
const column = new Array(completed.table.getHeight() - 1).fill(new TableCell(""));
const altered = insertColumn(completed.table, newFocus.column, column, options);
// format
const formatted = formatTable(altered, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, altered, formatted, true));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
this._moveToFocus(range.start.row, formatted.table, newFocus);
});
this.resetSmartCursor();
});
}
/**
* Deletes a column at the current focus.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
deleteColumn(options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
// move focus
if (newFocus.row === 1) {
newFocus = newFocus.setRow(0);
}
// delete a column
let altered = completed.table;
let moved = false;
if (0 <= newFocus.column && newFocus.column <= altered.getHeaderWidth() - 1) {
altered = deleteColumn(completed.table, newFocus.column, options);
moved = true;
if (newFocus.column > altered.getHeaderWidth() - 1) {
newFocus = newFocus.setColumn(altered.getHeaderWidth() - 1);
}
}
// format
const formatted = formatTable(altered, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, altered, formatted, moved));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
if (moved) {
this._selectFocus(range.start.row, formatted.table, newFocus);
}
else {
this._moveToFocus(range.start.row, formatted.table, newFocus);
}
});
this.resetSmartCursor();
});
}
/**
* Moves the focused column by the specified offset.
*
* @param {number} offset - An offset the column is moved by.
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
moveColumn(offset, options) {
this._withTable(({ range, lines, table, focus }) => {
let newFocus = focus;
// complete
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
// move column
let altered = completed.table;
if (0 <= newFocus.column && newFocus.column <= altered.getHeaderWidth() - 1) {
const dest = Math.min(Math.max(newFocus.column + offset, 0), altered.getHeaderWidth() - 1);
altered = moveColumn(altered, newFocus.column, dest);
newFocus = newFocus.setColumn(dest);
}
// format
const formatted = formatTable(altered, options);
newFocus = newFocus.setOffset(_computeNewOffset(newFocus, altered, formatted, false));
// apply
this._textEditor.transact(() => {
this._updateLines(range.start.row, range.end.row + 1, formatted.table.toLines(), lines);
this._moveToFocus(range.start.row, formatted.table, newFocus);
});
this.resetSmartCursor();
});
}
/**
* Formats all the tables in the text editor.
*
* @param {Object} options - See {@link options}.
* @returns {undefined}
*/
formatAll(options) {
this._textEditor.transact(() => {
let pos = this._textEditor.getCursorPosition();
let lines = [];
let startRow = undefined;
let lastRow = this._textEditor.getLastRow();
// find tables
for (let row = 0; row <= lastRow; row++) {
const line = this._textEditor.getLine(row);
if (this._textEditor.acceptsTableEdit(row) && _isTableRow(line)) {
lines.push(line);
if (startRow === undefined) {
startRow = row;
}
}
else if (startRow !== undefined) {
// get table info
const endRow = row - 1;
const range = new Range(
new Point(startRow, 0),
new Point(endRow, lines[lines.length - 1].length)
);
const table = readTable(lines);
const focus = table.focusOfPosition(pos, startRow);
const focused = focus !== undefined;
// format
let newFocus = focus;
const completed = completeTable(table, options);
if (focused && completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
const formatted = formatTable(completed.table, options);
if (focused) {
newFocus = newFocus.setOffset(
_computeNewOffset(newFocus, completed.table, formatted, false)
);
}
// apply
const newLines = formatted.table.toLines();
this._updateLines(range.start.row, range.end.row + 1, newLines, lines);
// update cursor position
const diff = newLines.length - lines.length;
if (focused) {
pos = formatted.table.positionOfFocus(newFocus, startRow);
}
else if (pos.row > endRow) {
pos = new Point(pos.row + diff, pos.column);
}
// reset
lines = [];
startRow = undefined;
// update
lastRow += diff;
row += diff;
}
}
if (startRow !== undefined) {
// get table info
const endRow = lastRow;
const range = new Range(
new Point(startRow, 0),
new Point(endRow, lines[lines.length - 1].length)
);
const table = readTable(lines);
const focus = table.focusOfPosition(pos, startRow);
// format
let newFocus = focus;
const completed = completeTable(table, options);
if (completed.delimiterInserted && newFocus.row > 0) {
newFocus = newFocus.setRow(newFocus.row + 1);
}
const formatted = formatTable(completed.table, options);
newFocus = newFocus.setOffset(
_computeNewOffset(newFocus, completed.table, formatted, false)
);
// apply
const newLines = formatted.table.toLines();
this._updateLines(range.start.row, range.end.row + 1, newLines, lines);
pos = formatted.table.positionOfFocus(newFocus, startRow);
}
this._textEditor.setCursorPosition(pos);
});
}
}
//# sourceMappingURL=mte-kernel.mjs.map
/***/ }),
/* 2 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return getEAW; });
/* unused harmony export computeWidth */
/*
* This file is generated by a script. DO NOT EDIT BY HAND!
*/
/*
* This part (from BEGIN to END) is derived from the Unicode Data Files:
*
* UNICODE, INC. LICENSE AGREEMENT
*
* Copyright © 1991-2017 Unicode, Inc. All rights reserved.
* Distributed under the Terms of Use in http://www.unicode.org/copyright.html.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of the Unicode data files and any associated documentation
* (the "Data Files") or Unicode software and any associated documentation
* (the "Software") to deal in the Data Files or Software
* without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, and/or sell copies of
* the Data Files or Software, and to permit persons to whom the Data Files
* or Software are furnished to do so, provided that either
* (a) this copyright and permission notice appear with all copies
* of the Data Files or Software, or
* (b) this copyright and permission notice appear in associated
* Documentation.
*
* THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
* ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT OF THIRD PARTY RIGHTS.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
* NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
* DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THE DATA FILES OR SOFTWARE.
*/
/* BEGIN */
var defs = [
{ start: 0, end: 31, prop: "N" },
{ start: 32, end: 126, prop: "Na" },
{ start: 127, end: 160, prop: "N" },
{ start: 161, end: 161, prop: "A" },
{ start: 162, end: 163, prop: "Na" },
{ start: 164, end: 164, prop: "A" },
{ start: 165, end: 166, prop: "Na" },
{ start: 167, end: 168, prop: "A" },
{ start: 169, end: 169, prop: "N" },
{ start: 170, end: 170, prop: "A" },
{ start: 171, end: 171, prop: "N" },
{ start: 172, end: 172, prop: "Na" },
{ start: 173, end: 174, prop: "A" },
{ start: 175, end: 175, prop: "Na" },
{ start: 176, end: 180, prop: "A" },
{ start: 181, end: 181, prop: "N" },
{ start: 182, end: 186, prop: "A" },
{ start: 187, end: 187, prop: "N" },
{ start: 188, end: 191, prop: "A" },
{ start: 192, end: 197, prop: "N" },
{ start: 198, end: 198, prop: "A" },
{ start: 199, end: 207, prop: "N" },
{ start: 208, end: 208, prop: "A" },
{ start: 209, end: 214, prop: "N" },
{ start: 215, end: 216, prop: "A" },
{ start: 217, end: 221, prop: "N" },
{ start: 222, end: 225, prop: "A" },
{ start: 226, end: 229, prop: "N" },
{ start: 230, end: 230, prop: "A" },
{ start: 231, end: 231, prop: "N" },
{ start: 232, end: 234, prop: "A" },
{ start: 235, end: 235, prop: "N" },
{ start: 236, end: 237, prop: "A" },
{ start: 238, end: 239, prop: "N" },
{ start: 240, end: 240, prop: "A" },
{ start: 241, end: 241, prop: "N" },
{ start: 242, end: 243, prop: "A" },
{ start: 244, end: 246, prop: "N" },
{ start: 247, end: 250, prop: "A" },
{ start: 251, end: 251, prop: "N" },
{ start: 252, end: 252, prop: "A" },
{ start: 253, end: 253, prop: "N" },
{ start: 254, end: 254, prop: "A" },
{ start: 255, end: 256, prop: "N" },
{ start: 257, end: 257, prop: "A" },
{ start: 258, end: 272, prop: "N" },
{ start: 273, end: 273, prop: "A" },
{ start: 274, end: 274, prop: "N" },
{ start: 275, end: 275, prop: "A" },
{ start: 276, end: 282, prop: "N" },
{ start: 283, end: 283, prop: "A" },
{ start: 284, end: 293, prop: "N" },
{ start: 294, end: 295, prop: "A" },
{ start: 296, end: 298, prop: "N" },
{ start: 299, end: 299, prop: "A" },
{ start: 300, end: 304, prop: "N" },
{ start: 305, end: 307, prop: "A" },
{ start: 308, end: 311, prop: "N" },
{ start: 312, end: 312, prop: "A" },
{ start: 313, end: 318, prop: "N" },
{ start: 319, end: 322, prop: "A" },
{ start: 323, end: 323, prop: "N" },
{ start: 324, end: 324, prop: "A" },
{ start: 325, end: 327, prop: "N" },
{ start: 328, end: 331, prop: "A" },
{ start: 332, end: 332, prop: "N" },
{ start: 333, end: 333, prop: "A" },
{ start: 334, end: 337, prop: "N" },
{ start: 338, end: 339, prop: "A" },
{ start: 340, end: 357, prop: "N" },
{ start: 358, end: 359, prop: "A" },
{ start: 360, end: 362, prop: "N" },
{ start: 363, end: 363, prop: "A" },
{ start: 364, end: 461, prop: "N" },
{ start: 462, end: 462, prop: "A" },
{ start: 463, end: 463, prop: "N" },
{ start: 464, end: 464, prop: "A" },
{ start: 465, end: 465, prop: "N" },
{ start: 466, end: 466, prop: "A" },
{ start: 467, end: 467, prop: "N" },
{ start: 468, end: 468, prop: "A" },
{ start: 469, end: 469, prop: "N" },
{ start: 470, end: 470, prop: "A" },
{ start: 471, end: 471, prop: "N" },
{ start: 472, end: 472, prop: "A" },
{ start: 473, end: 473, prop: "N" },
{ start: 474, end: 474, prop: "A" },
{ start: 475, end: 475, prop: "N" },
{ start: 476, end: 476, prop: "A" },
{ start: 477, end: 592, prop: "N" },
{ start: 593, end: 593, prop: "A" },
{ start: 594, end: 608, prop: "N" },
{ start: 609, end: 609, prop: "A" },
{ start: 610, end: 707, prop: "N" },
{ start: 708, end: 708, prop: "A" },
{ start: 709, end: 710, prop: "N" },
{ start: 711, end: 711, prop: "A" },
{ start: 712, end: 712, prop: "N" },
{ start: 713, end: 715, prop: "A" },
{ start: 716, end: 716, prop: "N" },
{ start: 717, end: 717, prop: "A" },
{ start: 718, end: 719, prop: "N" },
{ start: 720, end: 720, prop: "A" },
{ start: 721, end: 727, prop: "N" },
{ start: 728, end: 731, prop: "A" },
{ start: 732, end: 732, prop: "N" },
{ start: 733, end: 733, prop: "A" },
{ start: 734, end: 734, prop: "N" },
{ start: 735, end: 735, prop: "A" },
{ start: 736, end: 767, prop: "N" },
{ start: 768, end: 879, prop: "A" },
{ start: 880, end: 912, prop: "N" },
{ start: 913, end: 929, prop: "A" },
{ start: 930, end: 930, prop: "N" },
{ start: 931, end: 937, prop: "A" },
{ start: 938, end: 944, prop: "N" },
{ start: 945, end: 961, prop: "A" },
{ start: 962, end: 962, prop: "N" },
{ start: 963, end: 969, prop: "A" },
{ start: 970, end: 1024, prop: "N" },
{ start: 1025, end: 1025, prop: "A" },
{ start: 1026, end: 1039, prop: "N" },
{ start: 1040, end: 1103, prop: "A" },
{ start: 1104, end: 1104, prop: "N" },
{ start: 1105, end: 1105, prop: "A" },
{ start: 1106, end: 4351, prop: "N" },
{ start: 4352, end: 4447, prop: "W" },
{ start: 4448, end: 8207, prop: "N" },
{ start: 8208, end: 8208, prop: "A" },
{ start: 8209, end: 8210, prop: "N" },
{ start: 8211, end: 8214, prop: "A" },
{ start: 8215, end: 8215, prop: "N" },
{ start: 8216, end: 8217, prop: "A" },
{ start: 8218, end: 8219, prop: "N" },
{ start: 8220, end: 8221, prop: "A" },
{ start: 8222, end: 8223, prop: "N" },
{ start: 8224, end: 8226, prop: "A" },
{ start: 8227, end: 8227, prop: "N" },
{ start: 8228, end: 8231, prop: "A" },
{ start: 8232, end: 8239, prop: "N" },
{ start: 8240, end: 8240, prop: "A" },
{ start: 8241, end: 8241, prop: "N" },
{ start: 8242, end: 8243, prop: "A" },
{ start: 8244, end: 8244, prop: "N" },
{ start: 8245, end: 8245, prop: "A" },
{ start: 8246, end: 8250, prop: "N" },
{ start: 8251, end: 8251, prop: "A" },
{ start: 8252, end: 8253, prop: "N" },
{ start: 8254, end: 8254, prop: "A" },
{ start: 8255, end: 8307, prop: "N" },
{ start: 8308, end: 8308, prop: "A" },
{ start: 8309, end: 8318, prop: "N" },
{ start: 8319, end: 8319, prop: "A" },
{ start: 8320, end: 8320, prop: "N" },
{ start: 8321, end: 8324, prop: "A" },
{ start: 8325, end: 8360, prop: "N" },
{ start: 8361, end: 8361, prop: "H" },
{ start: 8362, end: 8363, prop: "N" },
{ start: 8364, end: 8364, prop: "A" },
{ start: 8365, end: 8450, prop: "N" },
{ start: 8451, end: 8451, prop: "A" },
{ start: 8452, end: 8452, prop: "N" },
{ start: 8453, end: 8453, prop: "A" },
{ start: 8454, end: 8456, prop: "N" },
{ start: 8457, end: 8457, prop: "A" },
{ start: 8458, end: 8466, prop: "N" },
{ start: 8467, end: 8467, prop: "A" },
{ start: 8468, end: 8469, prop: "N" },
{ start: 8470, end: 8470, prop: "A" },
{ start: 8471, end: 8480, prop: "N" },
{ start: 8481, end: 8482, prop: "A" },
{ start: 8483, end: 8485, prop: "N" },
{ start: 8486, end: 8486, prop: "A" },
{ start: 8487, end: 8490, prop: "N" },
{ start: 8491, end: 8491, prop: "A" },
{ start: 8492, end: 8530, prop: "N" },
{ start: 8531, end: 8532, prop: "A" },
{ start: 8533, end: 8538, prop: "N" },
{ start: 8539, end: 8542, prop: "A" },
{ start: 8543, end: 8543, prop: "N" },
{ start: 8544, end: 8555, prop: "A" },
{ start: 8556, end: 8559, prop: "N" },
{ start: 8560, end: 8569, prop: "A" },
{ start: 8570, end: 8584, prop: "N" },
{ start: 8585, end: 8585, prop: "A" },
{ start: 8586, end: 8591, prop: "N" },
{ start: 8592, end: 8601, prop: "A" },
{ start: 8602, end: 8631, prop: "N" },
{ start: 8632, end: 8633, prop: "A" },
{ start: 8634, end: 8657, prop: "N" },
{ start: 8658, end: 8658, prop: "A" },
{ start: 8659, end: 8659, prop: "N" },
{ start: 8660, end: 8660, prop: "A" },
{ start: 8661, end: 8678, prop: "N" },
{ start: 8679, end: 8679, prop: "A" },
{ start: 8680, end: 8703, prop: "N" },
{ start: 8704, end: 8704, prop: "A" },
{ start: 8705, end: 8705, prop: "N" },
{ start: 8706, end: 8707, prop: "A" },
{ start: 8708, end: 8710, prop: "N" },
{ start: 8711, end: 8712, prop: "A" },
{ start: 8713, end: 8714, prop: "N" },
{ start: 8715, end: 8715, prop: "A" },
{ start: 8716, end: 8718, prop: "N" },
{ start: 8719, end: 8719, prop: "A" },
{ start: 8720, end: 8720, prop: "N" },
{ start: 8721, end: 8721, prop: "A" },
{ start: 8722, end: 8724, prop: "N" },
{ start: 8725, end: 8725, prop: "A" },
{ start: 8726, end: 8729, prop: "N" },
{ start: 8730, end: 8730, prop: "A" },
{ start: 8731, end: 8732, prop: "N" },
{ start: 8733, end: 8736, prop: "A" },
{ start: 8737, end: 8738, prop: "N" },
{ start: 8739, end: 8739, prop: "A" },
{ start: 8740, end: 8740, prop: "N" },
{ start: 8741, end: 8741, prop: "A" },
{ start: 8742, end: 8742, prop: "N" },
{ start: 8743, end: 8748, prop: "A" },
{ start: 8749, end: 8749, prop: "N" },
{ start: 8750, end: 8750, prop: "A" },
{ start: 8751, end: 8755, prop: "N" },
{ start: 8756, end: 8759, prop: "A" },
{ start: 8760, end: 8763, prop: "N" },
{ start: 8764, end: 8765, prop: "A" },
{ start: 8766, end: 8775, prop: "N" },
{ start: 8776, end: 8776, prop: "A" },
{ start: 8777, end: 8779, prop: "N" },
{ start: 8780, end: 8780, prop: "A" },
{ start: 8781, end: 8785, prop: "N" },
{ start: 8786, end: 8786, prop: "A" },
{ start: 8787, end: 8799, prop: "N" },
{ start: 8800, end: 8801, prop: "A" },
{ start: 8802, end: 8803, prop: "N" },
{ start: 8804, end: 8807, prop: "A" },
{ start: 8808, end: 8809, prop: "N" },
{ start: 8810, end: 8811, prop: "A" },
{ start: 8812, end: 8813, prop: "N" },
{ start: 8814, end: 8815, prop: "A" },
{ start: 8816, end: 8833, prop: "N" },
{ start: 8834, end: 8835, prop: "A" },
{ start: 8836, end: 8837, prop: "N" },
{ start: 8838, end: 8839, prop: "A" },
{ start: 8840, end: 8852, prop: "N" },
{ start: 8853, end: 8853, prop: "A" },
{ start: 8854, end: 8856, prop: "N" },
{ start: 8857, end: 8857, prop: "A" },
{ start: 8858, end: 8868, prop: "N" },
{ start: 8869, end: 8869, prop: "A" },
{ start: 8870, end: 8894, prop: "N" },
{ start: 8895, end: 8895, prop: "A" },
{ start: 8896, end: 8977, prop: "N" },
{ start: 8978, end: 8978, prop: "A" },
{ start: 8979, end: 8985, prop: "N" },
{ start: 8986, end: 8987, prop: "W" },
{ start: 8988, end: 9000, prop: "N" },
{ start: 9001, end: 9002, prop: "W" },
{ start: 9003, end: 9192, prop: "N" },
{ start: 9193, end: 9196, prop: "W" },
{ start: 9197, end: 9199, prop: "N" },
{ start: 9200, end: 9200, prop: "W" },
{ start: 9201, end: 9202, prop: "N" },
{ start: 9203, end: 9203, prop: "W" },
{ start: 9204, end: 9311, prop: "N" },
{ start: 9312, end: 9449, prop: "A" },
{ start: 9450, end: 9450, prop: "N" },
{ start: 9451, end: 9547, prop: "A" },
{ start: 9548, end: 9551, prop: "N" },
{ start: 9552, end: 9587, prop: "A" },
{ start: 9588, end: 9599, prop: "N" },
{ start: 9600, end: 9615, prop: "A" },
{ start: 9616, end: 9617, prop: "N" },
{ start: 9618, end: 9621, prop: "A" },
{ start: 9622, end: 9631, prop: "N" },
{ start: 9632, end: 9633, prop: "A" },
{ start: 9634, end: 9634, prop: "N" },
{ start: 9635, end: 9641, prop: "A" },
{ start: 9642, end: 9649, prop: "N" },
{ start: 9650, end: 9651, prop: "A" },
{ start: 9652, end: 9653, prop: "N" },
{ start: 9654, end: 9655, prop: "A" },
{ start: 9656, end: 9659, prop: "N" },
{ start: 9660, end: 9661, prop: "A" },
{ start: 9662, end: 9663, prop: "N" },
{ start: 9664, end: 9665, prop: "A" },
{ start: 9666, end: 9669, prop: "N" },
{ start: 9670, end: 9672, prop: "A" },
{ start: 9673, end: 9674, prop: "N" },
{ start: 9675, end: 9675, prop: "A" },
{ start: 9676, end: 9677, prop: "N" },
{ start: 9678, end: 9681, prop: "A" },
{ start: 9682, end: 9697, prop: "N" },
{ start: 9698, end: 9701, prop: "A" },
{ start: 9702, end: 9710, prop: "N" },
{ start: 9711, end: 9711, prop: "A" },
{ start: 9712, end: 9724, prop: "N" },
{ start: 9725, end: 9726, prop: "W" },
{ start: 9727, end: 9732, prop: "N" },
{ start: 9733, end: 9734, prop: "A" },
{ start: 9735, end: 9736, prop: "N" },
{ start: 9737, end: 9737, prop: "A" },
{ start: 9738, end: 9741, prop: "N" },
{ start: 9742, end: 9743, prop: "A" },
{ start: 9744, end: 9747, prop: "N" },
{ start: 9748, end: 9749, prop: "W" },
{ start: 9750, end: 9755, prop: "N" },
{ start: 9756, end: 9756, prop: "A" },
{ start: 9757, end: 9757, prop: "N" },
{ start: 9758, end: 9758, prop: "A" },
{ start: 9759, end: 9791, prop: "N" },
{ start: 9792, end: 9792, prop: "A" },
{ start: 9793, end: 9793, prop: "N" },
{ start: 9794, end: 9794, prop: "A" },
{ start: 9795, end: 9799, prop: "N" },
{ start: 9800, end: 9811, prop: "W" },
{ start: 9812, end: 9823, prop: "N" },
{ start: 9824, end: 9825, prop: "A" },
{ start: 9826, end: 9826, prop: "N" },
{ start: 9827, end: 9829, prop: "A" },
{ start: 9830, end: 9830, prop: "N" },
{ start: 9831, end: 9834, prop: "A" },
{ start: 9835, end: 9835, prop: "N" },
{ start: 9836, end: 9837, prop: "A" },
{ start: 9838, end: 9838, prop: "N" },
{ start: 9839, end: 9839, prop: "A" },
{ start: 9840, end: 9854, prop: "N" },
{ start: 9855, end: 9855, prop: "W" },
{ start: 9856, end: 9874, prop: "N" },
{ start: 9875, end: 9875, prop: "W" },
{ start: 9876, end: 9885, prop: "N" },
{ start: 9886, end: 9887, prop: "A" },
{ start: 9888, end: 9888, prop: "N" },
{ start: 9889, end: 9889, prop: "W" },
{ start: 9890, end: 9897, prop: "N" },
{ start: 9898, end: 9899, prop: "W" },
{ start: 9900, end: 9916, prop: "N" },
{ start: 9917, end: 9918, prop: "W" },
{ start: 9919, end: 9919, prop: "A" },
{ start: 9920, end: 9923, prop: "N" },
{ start: 9924, end: 9925, prop: "W" },
{ start: 9926, end: 9933, prop: "A" },
{ start: 9934, end: 9934, prop: "W" },
{ start: 9935, end: 9939, prop: "A" },
{ start: 9940, end: 9940, prop: "W" },
{ start: 9941, end: 9953, prop: "A" },
{ start: 9954, end: 9954, prop: "N" },
{ start: 9955, end: 9955, prop: "A" },
{ start: 9956, end: 9959, prop: "N" },
{ start: 9960, end: 9961, prop: "A" },
{ start: 9962, end: 9962, prop: "W" },
{ start: 9963, end: 9969, prop: "A" },
{ start: 9970, end: 9971, prop: "W" },
{ start: 9972, end: 9972, prop: "A" },
{ start: 9973, end: 9973, prop: "W" },
{ start: 9974, end: 9977, prop: "A" },
{ start: 9978, end: 9978, prop: "W" },
{ start: 9979, end: 9980, prop: "A" },
{ start: 9981, end: 9981, prop: "W" },
{ start: 9982, end: 9983, prop: "A" },
{ start: 9984, end: 9988, prop: "N" },
{ start: 9989, end: 9989, prop: "W" },
{ start: 9990, end: 9993, prop: "N" },
{ start: 9994, end: 9995, prop: "W" },
{ start: 9996, end: 10023, prop: "N" },
{ start: 10024, end: 10024, prop: "W" },
{ start: 10025, end: 10044, prop: "N" },
{ start: 10045, end: 10045, prop: "A" },
{ start: 10046, end: 10059, prop: "N" },
{ start: 10060, end: 10060, prop: "W" },
{ start: 10061, end: 10061, prop: "N" },
{ start: 10062, end: 10062, prop: "W" },
{ start: 10063, end: 10066, prop: "N" },
{ start: 10067, end: 10069, prop: "W" },
{ start: 10070, end: 10070, prop: "N" },
{ start: 10071, end: 10071, prop: "W" },
{ start: 10072, end: 10101, prop: "N" },
{ start: 10102, end: 10111, prop: "A" },
{ start: 10112, end: 10132, prop: "N" },
{ start: 10133, end: 10135, prop: "W" },
{ start: 10136, end: 10159, prop: "N" },
{ start: 10160, end: 10160, prop: "W" },
{ start: 10161, end: 10174, prop: "N" },
{ start: 10175, end: 10175, prop: "W" },
{ start: 10176, end: 10213, prop: "N" },
{ start: 10214, end: 10221, prop: "Na" },
{ start: 10222, end: 10628, prop: "N" },
{ start: 10629, end: 10630, prop: "Na" },
{ start: 10631, end: 11034, prop: "N" },
{ start: 11035, end: 11036, prop: "W" },
{ start: 11037, end: 11087, prop: "N" },
{ start: 11088, end: 11088, prop: "W" },
{ start: 11089, end: 11092, prop: "N" },
{ start: 11093, end: 11093, prop: "W" },
{ start: 11094, end: 11097, prop: "A" },
{ start: 11098, end: 11903, prop: "N" },
{ start: 11904, end: 11929, prop: "W" },
{ start: 11930, end: 11930, prop: "N" },
{ start: 11931, end: 12019, prop: "W" },
{ start: 12020, end: 12031, prop: "N" },
{ start: 12032, end: 12245, prop: "W" },
{ start: 12246, end: 12271, prop: "N" },
{ start: 12272, end: 12283, prop: "W" },
{ start: 12284, end: 12287, prop: "N" },
{ start: 12288, end: 12288, prop: "F" },
{ start: 12289, end: 12350, prop: "W" },
{ start: 12351, end: 12352, prop: "N" },
{ start: 12353, end: 12438, prop: "W" },
{ start: 12439, end: 12440, prop: "N" },
{ start: 12441, end: 12543, prop: "W" },
{ start: 12544, end: 12548, prop: "N" },
{ start: 12549, end: 12590, prop: "W" },
{ start: 12591, end: 12592, prop: "N" },
{ start: 12593, end: 12686, prop: "W" },
{ start: 12687, end: 12687, prop: "N" },
{ start: 12688, end: 12730, prop: "W" },
{ start: 12731, end: 12735, prop: "N" },
{ start: 12736, end: 12771, prop: "W" },
{ start: 12772, end: 12783, prop: "N" },
{ start: 12784, end: 12830, prop: "W" },
{ start: 12831, end: 12831, prop: "N" },
{ start: 12832, end: 12871, prop: "W" },
{ start: 12872, end: 12879, prop: "A" },
{ start: 12880, end: 13054, prop: "W" },
{ start: 13055, end: 13055, prop: "N" },
{ start: 13056, end: 19903, prop: "W" },
{ start: 19904, end: 19967, prop: "N" },
{ start: 19968, end: 42124, prop: "W" },
{ start: 42125, end: 42127, prop: "N" },
{ start: 42128, end: 42182, prop: "W" },
{ start: 42183, end: 43359, prop: "N" },
{ start: 43360, end: 43388, prop: "W" },
{ start: 43389, end: 44031, prop: "N" },
{ start: 44032, end: 55203, prop: "W" },
{ start: 55204, end: 57343, prop: "N" },
{ start: 57344, end: 63743, prop: "A" },
{ start: 63744, end: 64255, prop: "W" },
{ start: 64256, end: 65023, prop: "N" },
{ start: 65024, end: 65039, prop: "A" },
{ start: 65040, end: 65049, prop: "W" },
{ start: 65050, end: 65071, prop: "N" },
{ start: 65072, end: 65106, prop: "W" },
{ start: 65107, end: 65107, prop: "N" },
{ start: 65108, end: 65126, prop: "W" },
{ start: 65127, end: 65127, prop: "N" },
{ start: 65128, end: 65131, prop: "W" },
{ start: 65132, end: 65280, prop: "N" },
{ start: 65281, end: 65376, prop: "F" },
{ start: 65377, end: 65470, prop: "H" },
{ start: 65471, end: 65473, prop: "N" },
{ start: 65474, end: 65479, prop: "H" },
{ start: 65480, end: 65481, prop: "N" },
{ start: 65482, end: 65487, prop: "H" },
{ start: 65488, end: 65489, prop: "N" },
{ start: 65490, end: 65495, prop: "H" },
{ start: 65496, end: 65497, prop: "N" },
{ start: 65498, end: 65500, prop: "H" },
{ start: 65501, end: 65503, prop: "N" },
{ start: 65504, end: 65510, prop: "F" },
{ start: 65511, end: 65511, prop: "N" },
{ start: 65512, end: 65518, prop: "H" },
{ start: 65519, end: 65532, prop: "N" },
{ start: 65533, end: 65533, prop: "A" },
{ start: 65534, end: 94175, prop: "N" },
{ start: 94176, end: 94177, prop: "W" },
{ start: 94178, end: 94207, prop: "N" },
{ start: 94208, end: 100332, prop: "W" },
{ start: 100333, end: 100351, prop: "N" },
{ start: 100352, end: 101106, prop: "W" },
{ start: 101107, end: 110591, prop: "N" },
{ start: 110592, end: 110878, prop: "W" },
{ start: 110879, end: 110959, prop: "N" },
{ start: 110960, end: 111355, prop: "W" },
{ start: 111356, end: 126979, prop: "N" },
{ start: 126980, end: 126980, prop: "W" },
{ start: 126981, end: 127182, prop: "N" },
{ start: 127183, end: 127183, prop: "W" },
{ start: 127184, end: 127231, prop: "N" },
{ start: 127232, end: 127242, prop: "A" },
{ start: 127243, end: 127247, prop: "N" },
{ start: 127248, end: 127277, prop: "A" },
{ start: 127278, end: 127279, prop: "N" },
{ start: 127280, end: 127337, prop: "A" },
{ start: 127338, end: 127343, prop: "N" },
{ start: 127344, end: 127373, prop: "A" },
{ start: 127374, end: 127374, prop: "W" },
{ start: 127375, end: 127376, prop: "A" },
{ start: 127377, end: 127386, prop: "W" },
{ start: 127387, end: 127404, prop: "A" },
{ start: 127405, end: 127487, prop: "N" },
{ start: 127488, end: 127490, prop: "W" },
{ start: 127491, end: 127503, prop: "N" },
{ start: 127504, end: 127547, prop: "W" },
{ start: 127548, end: 127551, prop: "N" },
{ start: 127552, end: 127560, prop: "W" },
{ start: 127561, end: 127567, prop: "N" },
{ start: 127568, end: 127569, prop: "W" },
{ start: 127570, end: 127583, prop: "N" },
{ start: 127584, end: 127589, prop: "W" },
{ start: 127590, end: 127743, prop: "N" },
{ start: 127744, end: 127776, prop: "W" },
{ start: 127777, end: 127788, prop: "N" },
{ start: 127789, end: 127797, prop: "W" },
{ start: 127798, end: 127798, prop: "N" },
{ start: 127799, end: 127868, prop: "W" },
{ start: 127869, end: 127869, prop: "N" },
{ start: 127870, end: 127891, prop: "W" },
{ start: 127892, end: 127903, prop: "N" },
{ start: 127904, end: 127946, prop: "W" },
{ start: 127947, end: 127950, prop: "N" },
{ start: 127951, end: 127955, prop: "W" },
{ start: 127956, end: 127967, prop: "N" },
{ start: 127968, end: 127984, prop: "W" },
{ start: 127985, end: 127987, prop: "N" },
{ start: 127988, end: 127988, prop: "W" },
{ start: 127989, end: 127991, prop: "N" },
{ start: 127992, end: 128062, prop: "W" },
{ start: 128063, end: 128063, prop: "N" },
{ start: 128064, end: 128064, prop: "W" },
{ start: 128065, end: 128065, prop: "N" },
{ start: 128066, end: 128252, prop: "W" },
{ start: 128253, end: 128254, prop: "N" },
{ start: 128255, end: 128317, prop: "W" },
{ start: 128318, end: 128330, prop: "N" },
{ start: 128331, end: 128334, prop: "W" },
{ start: 128335, end: 128335, prop: "N" },
{ start: 128336, end: 128359, prop: "W" },
{ start: 128360, end: 128377, prop: "N" },
{ start: 128378, end: 128378, prop: "W" },
{ start: 128379, end: 128404, prop: "N" },
{ start: 128405, end: 128406, prop: "W" },
{ start: 128407, end: 128419, prop: "N" },
{ start: 128420, end: 128420, prop: "W" },
{ start: 128421, end: 128506, prop: "N" },
{ start: 128507, end: 128591, prop: "W" },
{ start: 128592, end: 128639, prop: "N" },
{ start: 128640, end: 128709, prop: "W" },
{ start: 128710, end: 128715, prop: "N" },
{ start: 128716, end: 128716, prop: "W" },
{ start: 128717, end: 128719, prop: "N" },
{ start: 128720, end: 128722, prop: "W" },
{ start: 128723, end: 128746, prop: "N" },
{ start: 128747, end: 128748, prop: "W" },
{ start: 128749, end: 128755, prop: "N" },
{ start: 128756, end: 128760, prop: "W" },
{ start: 128761, end: 129295, prop: "N" },
{ start: 129296, end: 129342, prop: "W" },
{ start: 129343, end: 129343, prop: "N" },
{ start: 129344, end: 129356, prop: "W" },
{ start: 129357, end: 129359, prop: "N" },
{ start: 129360, end: 129387, prop: "W" },
{ start: 129388, end: 129407, prop: "N" },
{ start: 129408, end: 129431, prop: "W" },
{ start: 129432, end: 129471, prop: "N" },
{ start: 129472, end: 129472, prop: "W" },
{ start: 129473, end: 129487, prop: "N" },
{ start: 129488, end: 129510, prop: "W" },
{ start: 129511, end: 131071, prop: "N" },
{ start: 131072, end: 196605, prop: "W" },
{ start: 196606, end: 196607, prop: "N" },
{ start: 196608, end: 262141, prop: "W" },
{ start: 262142, end: 917759, prop: "N" },
{ start: 917760, end: 917999, prop: "A" },
{ start: 918000, end: 983039, prop: "N" },
{ start: 983040, end: 1048573, prop: "A" },
{ start: 1048574, end: 1048575, prop: "N" },
{ start: 1048576, end: 1114109, prop: "A" },
{ start: 1114110, end: 1114111, prop: "N" }
];
/* END */
/**
* Returns The EAW property of a code point.
* @private
* @param {string} codePoint A code point
* @return {string} The EAW property of the specified code point
*/
function _getEAWOfCodePoint(codePoint) {
let min = 0;
let max = defs.length - 1;
while (min !== max) {
const i = min + ((max - min) >> 1);
const def = defs[i];
if (codePoint < def.start) {
max = i - 1;
}
else if (codePoint > def.end) {
min = i + 1;
}
else {
return def.prop;
}
}
return defs[min].prop;
}
/**
* Returns the EAW property of a character.
* @param {string} str A string in which the character is contained
* @param {number} [at = 0] The position (in code unit) of the character in the string
* @return {string} The EAW property of the specified character
* @example
* import { getEAW } from "meaw";
*
* // Narrow
* assert(getEAW("A") === "Na");
* // Wide
* assert(getEAW("あ") === "W");
* assert(getEAW("安") === "W");
* assert(getEAW("🍣") === "W");
* // Fullwidth
* assert(getEAW("") === "F");
* // Halfwidth
* assert(getEAW("ア") === "H");
* // Ambiguous
* assert(getEAW("∀") === "A");
* assert(getEAW("→") === "A");
* assert(getEAW("Ω") === "A");
* assert(getEAW("Я") === "A");
* // Neutral
* assert(getEAW("ℵ") === "N");
*
* // a position (in code unit) can be specified
* assert(getEAW("ℵAあア∀", 2) === "W");
*/
function getEAW(str, at) {
const codePoint = str.codePointAt(at || 0);
return codePoint === undefined
? undefined
: _getEAWOfCodePoint(codePoint);
}
const defaultWidthMap = {
"N" : 1,
"Na": 1,
"W" : 2,
"F" : 2,
"H" : 1,
"A" : 1
};
/**
* Computes width of a string based on the EAW properties of its characters.
* By default characters with property Wide (W) or Fullwidth (F) are treated as wide (= 2)
* and the others are as narrow (= 1)
* @param {string} str A string to compute width
* @param {Object<string, number> | undefined} [widthMap = undefined]
* An object which represents a map from an EAW property to a character width
* @return {number} The computed width
* @example
* import { computeWidth } from "meaw";
*
* assert(computeWidth("Aあ🍣Ω") === 6);
* // custom widths can be specified by an object
* assert(computeWidth("Aあ🍣Ω", { "A": 2 }) === 7);
*/
function computeWidth(str, widthMap) {
const map = widthMap
? Object.assign({}, defaultWidthMap, widthMap)
: defaultWidthMap;
let width = 0;
for (const char of str) {
width += map[getEAW(char)];
}
return width;
}
//# sourceMappingURL=meaw.es.js.map
/***/ })
/******/ ]);
});