2022-02-28 11:12:37 +08:00
|
|
|
import { convertEndOfLineToChars } from "../common/end-of-line.js";
|
|
|
|
|
import getStringWidth from "../utils/get-string-width.js";
|
2023-12-17 19:34:12 +00:00
|
|
|
import { fill, hardlineWithoutBreakParent, indent } from "./builders.js";
|
2022-07-23 20:10:37 +08:00
|
|
|
import {
|
|
|
|
|
DOC_TYPE_ALIGN,
|
2023-12-17 19:34:12 +00:00
|
|
|
DOC_TYPE_ARRAY,
|
|
|
|
|
DOC_TYPE_BREAK_PARENT,
|
|
|
|
|
DOC_TYPE_CURSOR,
|
2022-07-23 20:10:37 +08:00
|
|
|
DOC_TYPE_FILL,
|
2023-12-17 19:34:12 +00:00
|
|
|
DOC_TYPE_GROUP,
|
2022-07-23 20:10:37 +08:00
|
|
|
DOC_TYPE_IF_BREAK,
|
2023-12-17 19:34:12 +00:00
|
|
|
DOC_TYPE_INDENT,
|
2022-07-23 20:10:37 +08:00
|
|
|
DOC_TYPE_INDENT_IF_BREAK,
|
2023-12-17 19:34:12 +00:00
|
|
|
DOC_TYPE_LABEL,
|
|
|
|
|
DOC_TYPE_LINE,
|
2022-07-23 20:10:37 +08:00
|
|
|
DOC_TYPE_LINE_SUFFIX,
|
|
|
|
|
DOC_TYPE_LINE_SUFFIX_BOUNDARY,
|
2023-12-17 19:34:12 +00:00
|
|
|
DOC_TYPE_STRING,
|
|
|
|
|
DOC_TYPE_TRIM,
|
2022-07-23 20:10:37 +08:00
|
|
|
} from "./constants.js";
|
|
|
|
|
import InvalidDocError from "./invalid-doc-error.js";
|
2023-12-17 19:34:12 +00:00
|
|
|
import { getDocParts, getDocType, propagateBreaks } from "./utils.js";
|
2017-05-11 00:13:21 +01:00
|
|
|
|
2022-07-23 20:45:25 +08:00
|
|
|
/** @typedef {typeof MODE_BREAK | typeof MODE_FLAT} Mode */
|
|
|
|
|
/** @typedef {{ ind: any, doc: any, mode: Mode }} Command */
|
2022-08-20 21:12:07 +08:00
|
|
|
/** @typedef {Record<symbol, Mode>} GroupModeMap */
|
2018-06-21 10:29:42 +08:00
|
|
|
|
2022-08-22 12:16:20 +08:00
|
|
|
/** @type {unique symbol} */
|
|
|
|
|
const MODE_BREAK = Symbol("MODE_BREAK");
|
|
|
|
|
/** @type {unique symbol} */
|
|
|
|
|
const MODE_FLAT = Symbol("MODE_FLAT");
|
2016-12-23 13:38:10 -05:00
|
|
|
|
2022-08-22 19:21:30 +08:00
|
|
|
const CURSOR_PLACEHOLDER = Symbol("cursor");
|
|
|
|
|
|
2017-04-06 23:49:37 -03:00
|
|
|
function rootIndent() {
|
2018-01-26 10:02:42 +08:00
|
|
|
return { value: "", length: 0, queue: [] };
|
2017-04-06 23:49:37 -03:00
|
|
|
}
|
|
|
|
|
|
2017-12-05 05:59:27 +08:00
|
|
|
function makeIndent(ind, options) {
|
2018-01-26 10:02:42 +08:00
|
|
|
return generateInd(ind, { type: "indent" }, options);
|
2017-04-06 23:49:37 -03:00
|
|
|
}
|
|
|
|
|
|
2021-03-11 04:14:08 +08:00
|
|
|
function makeAlign(indent, widthOrDoc, options) {
|
|
|
|
|
if (widthOrDoc === Number.NEGATIVE_INFINITY) {
|
2020-08-10 00:07:24 +08:00
|
|
|
return indent.root || rootIndent();
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-11 04:14:08 +08:00
|
|
|
if (widthOrDoc < 0) {
|
2020-08-10 00:07:24 +08:00
|
|
|
return generateInd(indent, { type: "dedent" }, options);
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-11 04:14:08 +08:00
|
|
|
if (!widthOrDoc) {
|
2020-08-10 00:07:24 +08:00
|
|
|
return indent;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-11 04:14:08 +08:00
|
|
|
if (widthOrDoc.type === "root") {
|
2020-08-10 00:07:24 +08:00
|
|
|
return { ...indent, root: indent };
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-11 04:14:08 +08:00
|
|
|
const alignType =
|
|
|
|
|
typeof widthOrDoc === "string" ? "stringAlign" : "numberAlign";
|
2020-08-10 00:07:24 +08:00
|
|
|
|
2021-03-11 04:14:08 +08:00
|
|
|
return generateInd(indent, { type: alignType, n: widthOrDoc }, options);
|
2018-01-26 10:02:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function generateInd(ind, newPart, options) {
|
|
|
|
|
const queue =
|
|
|
|
|
newPart.type === "dedent"
|
|
|
|
|
? ind.queue.slice(0, -1)
|
2021-01-26 09:46:14 +08:00
|
|
|
: [...ind.queue, newPart];
|
2018-01-26 10:02:42 +08:00
|
|
|
|
|
|
|
|
let value = "";
|
|
|
|
|
let length = 0;
|
|
|
|
|
let lastTabs = 0;
|
|
|
|
|
let lastSpaces = 0;
|
|
|
|
|
|
|
|
|
|
for (const part of queue) {
|
|
|
|
|
switch (part.type) {
|
|
|
|
|
case "indent":
|
|
|
|
|
flush();
|
|
|
|
|
if (options.useTabs) {
|
|
|
|
|
addTabs(1);
|
|
|
|
|
} else {
|
|
|
|
|
addSpaces(options.tabWidth);
|
2017-12-05 05:59:27 +08:00
|
|
|
}
|
2018-01-26 10:02:42 +08:00
|
|
|
break;
|
|
|
|
|
case "stringAlign":
|
|
|
|
|
flush();
|
|
|
|
|
value += part.n;
|
|
|
|
|
length += part.n.length;
|
|
|
|
|
break;
|
|
|
|
|
case "numberAlign":
|
|
|
|
|
lastTabs += 1;
|
|
|
|
|
lastSpaces += part.n;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
2022-10-22 23:33:08 +08:00
|
|
|
/* c8 ignore next */
|
2018-01-26 10:02:42 +08:00
|
|
|
throw new Error(`Unexpected type '${part.type}'`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flushSpaces();
|
|
|
|
|
|
2020-01-19 06:57:54 +08:00
|
|
|
return { ...ind, value, length, queue };
|
2018-01-26 10:02:42 +08:00
|
|
|
|
|
|
|
|
function addTabs(count) {
|
|
|
|
|
value += "\t".repeat(count);
|
|
|
|
|
length += options.tabWidth * count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addSpaces(count) {
|
|
|
|
|
value += " ".repeat(count);
|
|
|
|
|
length += count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function flush() {
|
|
|
|
|
if (options.useTabs) {
|
|
|
|
|
flushTabs();
|
|
|
|
|
} else {
|
|
|
|
|
flushSpaces();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function flushTabs() {
|
|
|
|
|
if (lastTabs > 0) {
|
|
|
|
|
addTabs(lastTabs);
|
|
|
|
|
}
|
|
|
|
|
resetLast();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function flushSpaces() {
|
|
|
|
|
if (lastSpaces > 0) {
|
|
|
|
|
addSpaces(lastSpaces);
|
|
|
|
|
}
|
|
|
|
|
resetLast();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetLast() {
|
|
|
|
|
lastTabs = 0;
|
|
|
|
|
lastSpaces = 0;
|
|
|
|
|
}
|
2017-04-06 23:49:37 -03:00
|
|
|
}
|
|
|
|
|
|
2022-08-31 17:46:22 +08:00
|
|
|
// Trim `Tab(U+0009)` and `Space(U+0020)` at the end of line
|
2018-10-06 14:18:52 +02:00
|
|
|
function trim(out) {
|
|
|
|
|
let trimCount = 0;
|
2022-09-06 15:57:47 +03:00
|
|
|
let cursorCount = 0;
|
|
|
|
|
let outIndex = out.length;
|
2018-10-06 14:18:52 +02:00
|
|
|
|
2022-09-06 15:57:47 +03:00
|
|
|
outer: while (outIndex--) {
|
|
|
|
|
const last = out[outIndex];
|
|
|
|
|
|
|
|
|
|
if (last === CURSOR_PLACEHOLDER) {
|
|
|
|
|
cursorCount++;
|
|
|
|
|
continue;
|
2022-08-31 17:46:22 +08:00
|
|
|
}
|
2018-10-06 14:18:52 +02:00
|
|
|
|
2022-10-22 23:33:08 +08:00
|
|
|
/* c8 ignore next 3 */
|
2022-09-06 15:57:47 +03:00
|
|
|
if (process.env.NODE_ENV !== "production" && typeof last !== "string") {
|
|
|
|
|
throw new Error(`Unexpected value in trim: '${typeof last}'`);
|
2022-08-31 17:46:22 +08:00
|
|
|
}
|
|
|
|
|
|
2022-09-06 15:57:47 +03:00
|
|
|
// Not using a regexp here because regexps for trimming off trailing
|
|
|
|
|
// characters are known to have performance issues.
|
|
|
|
|
for (let charIndex = last.length - 1; charIndex >= 0; charIndex--) {
|
|
|
|
|
const char = last[charIndex];
|
|
|
|
|
if (char === " " || char === "\t") {
|
|
|
|
|
trimCount++;
|
|
|
|
|
} else {
|
|
|
|
|
out[outIndex] = last.slice(0, charIndex + 1);
|
|
|
|
|
break outer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (trimCount > 0 || cursorCount > 0) {
|
|
|
|
|
out.length = outIndex + 1;
|
|
|
|
|
|
|
|
|
|
while (cursorCount-- > 0) {
|
|
|
|
|
out.push(CURSOR_PLACEHOLDER);
|
|
|
|
|
}
|
2018-10-06 14:18:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return trimCount;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-23 20:45:25 +08:00
|
|
|
/**
|
|
|
|
|
* @param {Command} next
|
|
|
|
|
* @param {Command[]} restCommands
|
|
|
|
|
* @param {number} width
|
|
|
|
|
* @param {boolean} hasLineSuffix
|
2022-08-20 21:12:07 +08:00
|
|
|
* @param {GroupModeMap} groupModeMap
|
2022-07-23 20:45:25 +08:00
|
|
|
* @param {boolean} [mustBeFlat]
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
2022-08-20 21:12:07 +08:00
|
|
|
function fits(
|
|
|
|
|
next,
|
|
|
|
|
restCommands,
|
|
|
|
|
width,
|
|
|
|
|
hasLineSuffix,
|
|
|
|
|
groupModeMap,
|
|
|
|
|
mustBeFlat,
|
|
|
|
|
) {
|
2023-03-31 16:47:26 +08:00
|
|
|
if (width === Number.POSITIVE_INFINITY) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-23 13:38:10 -05:00
|
|
|
let restIdx = restCommands.length;
|
2022-07-23 20:45:25 +08:00
|
|
|
/** @type {Array<Omit<Command, 'ind'>>} */
|
2017-01-28 07:50:22 -08:00
|
|
|
const cmds = [next];
|
2018-10-06 14:18:52 +02:00
|
|
|
// `out` is only used for width counting because `trim` requires to look
|
|
|
|
|
// backwards for space characters.
|
|
|
|
|
const out = [];
|
2017-01-05 12:21:57 -05:00
|
|
|
while (width >= 0) {
|
|
|
|
|
if (cmds.length === 0) {
|
|
|
|
|
if (restIdx === 0) {
|
2016-12-23 13:38:10 -05:00
|
|
|
return true;
|
|
|
|
|
}
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push(restCommands[--restIdx]);
|
2017-06-05 18:12:59 -04:00
|
|
|
|
|
|
|
|
continue;
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
|
|
|
|
|
2022-07-23 20:45:25 +08:00
|
|
|
const { mode, doc } = cmds.pop();
|
2017-01-05 12:21:57 -05:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
switch (getDocType(doc)) {
|
|
|
|
|
case DOC_TYPE_STRING:
|
|
|
|
|
out.push(doc);
|
|
|
|
|
width -= getStringWidth(doc);
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-08-06 11:03:47 +08:00
|
|
|
case DOC_TYPE_ARRAY:
|
2022-07-23 20:45:25 +08:00
|
|
|
case DOC_TYPE_FILL: {
|
2022-07-23 20:10:37 +08:00
|
|
|
const parts = getDocParts(doc);
|
|
|
|
|
for (let i = parts.length - 1; i >= 0; i--) {
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({ mode, doc: parts[i] });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2021-01-04 10:04:30 +08:00
|
|
|
}
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_INDENT:
|
|
|
|
|
case DOC_TYPE_ALIGN:
|
2022-07-23 20:45:25 +08:00
|
|
|
case DOC_TYPE_INDENT_IF_BREAK:
|
|
|
|
|
case DOC_TYPE_LABEL:
|
|
|
|
|
cmds.push({ mode, doc: doc.contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_TRIM:
|
|
|
|
|
width += trim(out);
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_GROUP: {
|
|
|
|
|
if (mustBeFlat && doc.break) {
|
|
|
|
|
return false;
|
2021-02-16 21:25:11 +02:00
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
const groupMode = doc.break ? MODE_BREAK : mode;
|
2022-07-23 20:45:25 +08:00
|
|
|
// The most expanded state takes up the least space on the current line.
|
|
|
|
|
const contents =
|
2022-07-23 20:10:37 +08:00
|
|
|
doc.expandedStates && groupMode === MODE_BREAK
|
2022-09-18 17:50:14 +08:00
|
|
|
? doc.expandedStates.at(-1)
|
2022-07-23 20:45:25 +08:00
|
|
|
: doc.contents;
|
|
|
|
|
cmds.push({ mode: groupMode, doc: contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
|
|
|
|
}
|
2018-10-06 14:18:52 +02:00
|
|
|
|
2022-07-23 20:45:25 +08:00
|
|
|
case DOC_TYPE_IF_BREAK: {
|
2022-07-23 20:10:37 +08:00
|
|
|
const groupMode = doc.groupId
|
|
|
|
|
? groupModeMap[doc.groupId] || MODE_FLAT
|
|
|
|
|
: mode;
|
2022-07-23 20:45:25 +08:00
|
|
|
const contents =
|
|
|
|
|
groupMode === MODE_BREAK ? doc.breakContents : doc.flatContents;
|
|
|
|
|
if (contents) {
|
|
|
|
|
cmds.push({ mode, doc: contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LINE:
|
2022-07-23 20:45:25 +08:00
|
|
|
if (mode === MODE_BREAK || doc.hard) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (!doc.soft) {
|
|
|
|
|
out.push(" ");
|
|
|
|
|
width--;
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LINE_SUFFIX:
|
|
|
|
|
hasLineSuffix = true;
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LINE_SUFFIX_BOUNDARY:
|
|
|
|
|
if (hasLineSuffix) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-06 23:49:37 -03:00
|
|
|
function printDocToString(doc, options) {
|
2022-08-20 21:12:07 +08:00
|
|
|
/** @type GroupModeMap */
|
|
|
|
|
const groupModeMap = {};
|
2018-06-21 10:29:42 +08:00
|
|
|
|
2017-05-21 21:11:09 +02:00
|
|
|
const width = options.printWidth;
|
2018-11-06 14:47:13 +00:00
|
|
|
const newLine = convertEndOfLineToChars(options.endOfLine);
|
2016-12-23 13:38:10 -05:00
|
|
|
let pos = 0;
|
|
|
|
|
// cmds is basically a stack. We've turned a recursive call into a
|
|
|
|
|
// while loop which is much faster. The while loop below adds new
|
|
|
|
|
// cmds to the array instead of recursively calling `print`.
|
2022-07-23 20:45:25 +08:00
|
|
|
/** @type Command[] */
|
|
|
|
|
const cmds = [{ ind: rootIndent(), mode: MODE_BREAK, doc }];
|
2017-05-21 21:11:09 +02:00
|
|
|
const out = [];
|
2017-01-04 15:24:10 -05:00
|
|
|
let shouldRemeasure = false;
|
2022-07-23 20:45:25 +08:00
|
|
|
/** @type Command[] */
|
|
|
|
|
const lineSuffix = [];
|
2022-08-26 21:33:49 +08:00
|
|
|
let printedCursorCount = 0;
|
2017-01-26 14:57:43 -05:00
|
|
|
|
2023-03-07 20:42:14 +08:00
|
|
|
propagateBreaks(doc);
|
|
|
|
|
|
2021-01-04 10:11:57 +08:00
|
|
|
while (cmds.length > 0) {
|
2022-07-23 20:45:25 +08:00
|
|
|
const { ind, mode, doc } = cmds.pop();
|
2022-07-23 20:10:37 +08:00
|
|
|
switch (getDocType(doc)) {
|
|
|
|
|
case DOC_TYPE_STRING: {
|
2022-11-21 23:41:27 +08:00
|
|
|
const formatted =
|
|
|
|
|
newLine !== "\n" ? doc.replaceAll("\n", newLine) : doc;
|
2022-07-23 20:10:37 +08:00
|
|
|
out.push(formatted);
|
2022-12-19 13:30:50 +08:00
|
|
|
// Plugins may print single string, should skip measure the width
|
|
|
|
|
if (cmds.length > 0) {
|
|
|
|
|
pos += getStringWidth(formatted);
|
|
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2021-01-04 10:04:30 +08:00
|
|
|
}
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-10-08 10:44:30 +08:00
|
|
|
case DOC_TYPE_ARRAY:
|
2022-08-06 11:03:47 +08:00
|
|
|
for (let i = doc.length - 1; i >= 0; i--) {
|
|
|
|
|
cmds.push({ ind, mode, doc: doc[i] });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_CURSOR:
|
2022-08-26 21:33:49 +08:00
|
|
|
if (printedCursorCount >= 2) {
|
|
|
|
|
throw new Error("There are too many 'cursor' in doc.");
|
|
|
|
|
}
|
2022-08-22 19:21:30 +08:00
|
|
|
out.push(CURSOR_PLACEHOLDER);
|
2022-08-26 21:33:49 +08:00
|
|
|
printedCursorCount++;
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_INDENT:
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({ ind: makeIndent(ind, options), mode, doc: doc.contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_ALIGN:
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({
|
|
|
|
|
ind: makeAlign(ind, doc.n, options),
|
|
|
|
|
mode,
|
|
|
|
|
doc: doc.contents,
|
|
|
|
|
});
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_TRIM:
|
|
|
|
|
pos -= trim(out);
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_GROUP:
|
|
|
|
|
switch (mode) {
|
|
|
|
|
case MODE_FLAT:
|
|
|
|
|
if (!shouldRemeasure) {
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({
|
2022-07-23 20:10:37 +08:00
|
|
|
ind,
|
2022-07-23 20:45:25 +08:00
|
|
|
mode: doc.break ? MODE_BREAK : MODE_FLAT,
|
|
|
|
|
doc: doc.contents,
|
|
|
|
|
});
|
2017-01-13 15:03:53 -05:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// fallthrough
|
2017-01-13 15:03:53 -05:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case MODE_BREAK: {
|
|
|
|
|
shouldRemeasure = false;
|
2017-01-13 15:03:53 -05:00
|
|
|
|
2022-07-23 20:45:25 +08:00
|
|
|
const next = { ind, mode: MODE_FLAT, doc: doc.contents };
|
2022-07-23 20:10:37 +08:00
|
|
|
const rem = width - pos;
|
|
|
|
|
const hasLineSuffix = lineSuffix.length > 0;
|
2017-01-13 15:03:53 -05:00
|
|
|
|
2022-08-20 21:12:07 +08:00
|
|
|
if (
|
|
|
|
|
!doc.break &&
|
|
|
|
|
fits(next, cmds, rem, hasLineSuffix, groupModeMap)
|
|
|
|
|
) {
|
2022-07-23 20:10:37 +08:00
|
|
|
cmds.push(next);
|
|
|
|
|
} else {
|
|
|
|
|
// Expanded states are a rare case where a document
|
|
|
|
|
// can manually provide multiple representations of
|
|
|
|
|
// itself. It provides an array of documents
|
|
|
|
|
// going from the least expanded (most flattened)
|
|
|
|
|
// representation first to the most expanded. If a
|
|
|
|
|
// group has these, we need to manually go through
|
|
|
|
|
// these states and find the first one that fits.
|
2022-09-29 05:25:54 +03:00
|
|
|
// eslint-disable-next-line no-lonely-if
|
2022-07-23 20:10:37 +08:00
|
|
|
if (doc.expandedStates) {
|
2022-09-18 17:50:14 +08:00
|
|
|
const mostExpanded = doc.expandedStates.at(-1);
|
2017-01-05 12:21:57 -05:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
if (doc.break) {
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({ ind, mode: MODE_BREAK, doc: mostExpanded });
|
2022-07-23 20:10:37 +08:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
for (let i = 1; i < doc.expandedStates.length + 1; i++) {
|
|
|
|
|
if (i >= doc.expandedStates.length) {
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({ ind, mode: MODE_BREAK, doc: mostExpanded });
|
2022-07-23 20:10:37 +08:00
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
const state = doc.expandedStates[i];
|
2022-07-23 20:45:25 +08:00
|
|
|
const cmd = { ind, mode: MODE_FLAT, doc: state };
|
2022-07-23 20:10:37 +08:00
|
|
|
|
2022-08-20 21:12:07 +08:00
|
|
|
if (fits(cmd, cmds, rem, hasLineSuffix, groupModeMap)) {
|
2022-07-23 20:10:37 +08:00
|
|
|
cmds.push(cmd);
|
2017-01-05 12:21:57 -05:00
|
|
|
|
2017-01-13 15:03:53 -05:00
|
|
|
break;
|
|
|
|
|
}
|
2017-01-04 15:24:10 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
} else {
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({ ind, mode: MODE_BREAK, doc: doc.contents });
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
2017-05-21 21:11:09 +02:00
|
|
|
}
|
2018-06-21 10:29:42 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2018-06-21 10:29:42 +08:00
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (doc.id) {
|
2022-09-18 17:50:14 +08:00
|
|
|
groupModeMap[doc.id] = cmds.at(-1).mode;
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
// Fills each line with as much code as possible before moving to a new
|
|
|
|
|
// line with the same indentation.
|
|
|
|
|
//
|
|
|
|
|
// Expects doc.parts to be an array of alternating content and
|
|
|
|
|
// whitespace. The whitespace contains the linebreaks.
|
|
|
|
|
//
|
|
|
|
|
// For example:
|
|
|
|
|
// ["I", line, "love", line, "monkeys"]
|
|
|
|
|
// or
|
|
|
|
|
// [{ type: group, ... }, softline, { type: group, ... }]
|
|
|
|
|
//
|
|
|
|
|
// It uses this parts structure to handle three main layout cases:
|
|
|
|
|
// * The first two content items fit on the same line without
|
|
|
|
|
// breaking
|
|
|
|
|
// -> output the first content item and the whitespace "flat".
|
|
|
|
|
// * Only the first content item fits on the line without breaking
|
|
|
|
|
// -> output the first content item "flat" and the whitespace with
|
|
|
|
|
// "break".
|
|
|
|
|
// * Neither content item fits on the line without breaking
|
|
|
|
|
// -> output the first content item and the whitespace with "break".
|
|
|
|
|
case DOC_TYPE_FILL: {
|
|
|
|
|
const rem = width - pos;
|
|
|
|
|
|
|
|
|
|
const { parts } = doc;
|
|
|
|
|
if (parts.length === 0) {
|
2017-01-13 15:03:53 -05:00
|
|
|
break;
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
2017-05-11 00:13:21 +01:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
const [content, whitespace] = parts;
|
2022-07-23 20:45:25 +08:00
|
|
|
const contentFlatCmd = { ind, mode: MODE_FLAT, doc: content };
|
|
|
|
|
const contentBreakCmd = { ind, mode: MODE_BREAK, doc: content };
|
2022-07-23 20:10:37 +08:00
|
|
|
const contentFits = fits(
|
|
|
|
|
contentFlatCmd,
|
|
|
|
|
[],
|
|
|
|
|
rem,
|
|
|
|
|
lineSuffix.length > 0,
|
2022-08-20 21:12:07 +08:00
|
|
|
groupModeMap,
|
2022-07-23 20:10:37 +08:00
|
|
|
true,
|
|
|
|
|
);
|
2017-05-11 00:13:21 +01:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
if (parts.length === 1) {
|
|
|
|
|
if (contentFits) {
|
|
|
|
|
cmds.push(contentFlatCmd);
|
2017-05-11 00:13:21 +01:00
|
|
|
} else {
|
2022-07-23 20:10:37 +08:00
|
|
|
cmds.push(contentBreakCmd);
|
2017-05-11 00:13:21 +01:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
2016-12-31 14:38:33 -05:00
|
|
|
|
2022-07-23 20:45:25 +08:00
|
|
|
const whitespaceFlatCmd = { ind, mode: MODE_FLAT, doc: whitespace };
|
|
|
|
|
const whitespaceBreakCmd = { ind, mode: MODE_BREAK, doc: whitespace };
|
2022-07-23 20:10:37 +08:00
|
|
|
|
|
|
|
|
if (parts.length === 2) {
|
|
|
|
|
if (contentFits) {
|
|
|
|
|
cmds.push(whitespaceFlatCmd, contentFlatCmd);
|
|
|
|
|
} else {
|
|
|
|
|
cmds.push(whitespaceBreakCmd, contentBreakCmd);
|
|
|
|
|
}
|
2017-01-26 14:57:43 -05:00
|
|
|
break;
|
2018-06-21 10:29:42 +08:00
|
|
|
}
|
2017-01-05 12:21:57 -05:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
// At this point we've handled the first pair (context, separator)
|
|
|
|
|
// and will create a new fill doc for the rest of the content.
|
|
|
|
|
// Ideally we wouldn't mutate the array here but copying all the
|
|
|
|
|
// elements to a new array would make this algorithm quadratic,
|
|
|
|
|
// which is unusable for large arrays (e.g. large texts in JSX).
|
|
|
|
|
parts.splice(0, 2);
|
2022-07-23 20:45:25 +08:00
|
|
|
const remainingCmd = { ind, mode, doc: fill(parts) };
|
2017-01-05 12:21:57 -05:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
const secondContent = parts[0];
|
2017-05-21 21:11:09 +02:00
|
|
|
|
2022-07-23 20:45:25 +08:00
|
|
|
const firstAndSecondContentFlatCmd = {
|
2022-07-23 20:10:37 +08:00
|
|
|
ind,
|
2022-07-23 20:45:25 +08:00
|
|
|
mode: MODE_FLAT,
|
|
|
|
|
doc: [content, whitespace, secondContent],
|
|
|
|
|
};
|
2022-07-23 20:10:37 +08:00
|
|
|
const firstAndSecondContentFits = fits(
|
|
|
|
|
firstAndSecondContentFlatCmd,
|
|
|
|
|
[],
|
|
|
|
|
rem,
|
|
|
|
|
lineSuffix.length > 0,
|
2022-08-20 21:12:07 +08:00
|
|
|
groupModeMap,
|
2022-07-23 20:10:37 +08:00
|
|
|
true,
|
|
|
|
|
);
|
2017-02-13 07:13:16 -08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
if (firstAndSecondContentFits) {
|
|
|
|
|
cmds.push(remainingCmd, whitespaceFlatCmd, contentFlatCmd);
|
|
|
|
|
} else if (contentFits) {
|
|
|
|
|
cmds.push(remainingCmd, whitespaceBreakCmd, contentFlatCmd);
|
|
|
|
|
} else {
|
|
|
|
|
cmds.push(remainingCmd, whitespaceBreakCmd, contentBreakCmd);
|
|
|
|
|
}
|
|
|
|
|
break;
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_IF_BREAK:
|
|
|
|
|
case DOC_TYPE_INDENT_IF_BREAK: {
|
|
|
|
|
const groupMode = doc.groupId ? groupModeMap[doc.groupId] : mode;
|
|
|
|
|
if (groupMode === MODE_BREAK) {
|
|
|
|
|
const breakContents =
|
|
|
|
|
doc.type === DOC_TYPE_IF_BREAK
|
|
|
|
|
? doc.breakContents
|
|
|
|
|
: doc.negate
|
|
|
|
|
? doc.contents
|
|
|
|
|
: indent(doc.contents);
|
|
|
|
|
if (breakContents) {
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({ ind, mode, doc: breakContents });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (groupMode === MODE_FLAT) {
|
|
|
|
|
const flatContents =
|
|
|
|
|
doc.type === DOC_TYPE_IF_BREAK
|
|
|
|
|
? doc.flatContents
|
|
|
|
|
: doc.negate
|
|
|
|
|
? indent(doc.contents)
|
|
|
|
|
: doc.contents;
|
|
|
|
|
if (flatContents) {
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({ ind, mode, doc: flatContents });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case DOC_TYPE_LINE_SUFFIX:
|
2022-07-23 20:45:25 +08:00
|
|
|
lineSuffix.push({ ind, mode, doc: doc.contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LINE_SUFFIX_BOUNDARY:
|
|
|
|
|
if (lineSuffix.length > 0) {
|
2022-07-23 20:48:21 +08:00
|
|
|
cmds.push({ ind, mode, doc: hardlineWithoutBreakParent });
|
2022-07-23 20:10:37 +08:00
|
|
|
}
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LINE:
|
|
|
|
|
switch (mode) {
|
|
|
|
|
case MODE_FLAT:
|
|
|
|
|
if (!doc.hard) {
|
|
|
|
|
if (!doc.soft) {
|
|
|
|
|
out.push(" ");
|
|
|
|
|
|
|
|
|
|
pos += 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
} else {
|
|
|
|
|
// This line was forced into the output even if we
|
|
|
|
|
// were in flattened mode, so we need to tell the next
|
|
|
|
|
// group that no matter what, it needs to remeasure
|
|
|
|
|
// because the previous measurement didn't accurately
|
|
|
|
|
// capture the entire expression (this is necessary
|
|
|
|
|
// for nested groups)
|
|
|
|
|
shouldRemeasure = true;
|
|
|
|
|
}
|
|
|
|
|
// fallthrough
|
|
|
|
|
|
|
|
|
|
case MODE_BREAK:
|
|
|
|
|
if (lineSuffix.length > 0) {
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({ ind, mode, doc }, ...lineSuffix.reverse());
|
|
|
|
|
lineSuffix.length = 0;
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (doc.literal) {
|
|
|
|
|
if (ind.root) {
|
|
|
|
|
out.push(newLine, ind.root.value);
|
|
|
|
|
pos = ind.root.length;
|
|
|
|
|
} else {
|
|
|
|
|
out.push(newLine);
|
|
|
|
|
pos = 0;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
pos -= trim(out);
|
|
|
|
|
out.push(newLine + ind.value);
|
|
|
|
|
pos = ind.length;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_LABEL:
|
2022-07-23 20:45:25 +08:00
|
|
|
cmds.push({ ind, mode, doc: doc.contents });
|
2022-07-23 20:10:37 +08:00
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
case DOC_TYPE_BREAK_PARENT:
|
|
|
|
|
// No op
|
|
|
|
|
break;
|
2022-07-23 20:45:25 +08:00
|
|
|
|
2022-07-23 20:10:37 +08:00
|
|
|
default:
|
|
|
|
|
throw new InvalidDocError(doc);
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
2020-11-18 05:58:46 -05:00
|
|
|
|
|
|
|
|
// Flush remaining line-suffix contents at the end of the document, in case
|
|
|
|
|
// there is no new line after the line-suffix.
|
2021-01-04 10:11:57 +08:00
|
|
|
if (cmds.length === 0 && lineSuffix.length > 0) {
|
2020-11-18 05:58:46 -05:00
|
|
|
cmds.push(...lineSuffix.reverse());
|
2022-07-23 20:45:25 +08:00
|
|
|
lineSuffix.length = 0;
|
2020-11-18 05:58:46 -05:00
|
|
|
}
|
2017-01-05 12:21:57 -05:00
|
|
|
}
|
2017-06-01 18:52:29 -04:00
|
|
|
|
2022-08-22 19:21:30 +08:00
|
|
|
const cursorPlaceholderIndex = out.indexOf(CURSOR_PLACEHOLDER);
|
2017-06-01 18:52:29 -04:00
|
|
|
if (cursorPlaceholderIndex !== -1) {
|
2018-05-01 13:42:59 +01:00
|
|
|
const otherCursorPlaceholderIndex = out.indexOf(
|
2022-08-22 19:21:30 +08:00
|
|
|
CURSOR_PLACEHOLDER,
|
2018-05-01 13:42:59 +01:00
|
|
|
cursorPlaceholderIndex + 1,
|
|
|
|
|
);
|
2017-06-01 18:52:29 -04:00
|
|
|
const beforeCursor = out.slice(0, cursorPlaceholderIndex).join("");
|
2018-05-01 13:42:59 +01:00
|
|
|
const aroundCursor = out
|
|
|
|
|
.slice(cursorPlaceholderIndex + 1, otherCursorPlaceholderIndex)
|
|
|
|
|
.join("");
|
|
|
|
|
const afterCursor = out.slice(otherCursorPlaceholderIndex + 1).join("");
|
2017-06-01 18:52:29 -04:00
|
|
|
|
|
|
|
|
return {
|
2018-05-01 13:42:59 +01:00
|
|
|
formatted: beforeCursor + aroundCursor + afterCursor,
|
|
|
|
|
cursorNodeStart: beforeCursor.length,
|
|
|
|
|
cursorNodeText: aroundCursor,
|
2017-06-01 18:52:29 -04:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return { formatted: out.join("") };
|
2016-12-23 13:38:10 -05:00
|
|
|
}
|
|
|
|
|
|
2022-02-28 11:12:37 +08:00
|
|
|
export { printDocToString };
|