Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/src/packages/frontend/codemirror/addon/fill-paragraph.ts
Views: 687
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/* Analogue of fill-paragraph from emacs. It's basically what is described6in here, but not *exactly*.78https://www.gnu.org/software/emacs/manual/html_node/emacs/Fill-Commands.html9*/1011import { defineExtension, Editor } from "codemirror";12import { Pos } from "./types";13import { split } from "@cocalc/util/misc";1415function is_white_space(s: string): boolean {16return s?.trim() == "";17}1819defineExtension("fill_paragraph", function (opts: { cols?: number }): void {20// @ts-ignore21const cm: Editor = this;22const pos: Pos = cm.getCursor();2324const cols = opts?.cols ?? 80;2526let n = pos.line;27// enforce emacs rule when cursor is between paragraphs: go to next para28while (is_white_space(cm.getLine(n)) && n < cm.lastLine()) {29n += 1;30}3132// find the start line and end line of the current paragraph33let start = n;34while (start > cm.firstLine() && !is_white_space(cm.getLine(start))) {35start -= 1;36}37while (is_white_space(cm.getLine(start)) && start < cm.lastLine()) {38start += 1;39}40let end = n;41while (end < cm.lastLine() && !is_white_space(cm.getLine(end))) {42end += 1;43}44while (is_white_space(cm.getLine(end)) && end > cm.firstLine()) {45end -= 1;46}4748// Now start and end are valid lines in the document and they are where the49// paragraph actually starts and ends. They are not whitespace lines, unless50// document is entirely whitespace.51const from = { line: start, ch: 0 };52const to = { line: end, ch: cm.getLine(end).length + 1 };53let para = cm.getRange(from, to);5455// Find a single character not in the range and put it at the cursor, so56// we can track where the cursor goes when we do the replacement.57// We only have to do this if the cursor is in the range.58let cursor: string = "";59if (pos.line >= start && pos.line <= end) {60let code = 1000;61cursor = String.fromCharCode(code);62while (para.indexOf(cursor) != -1) {63code += 1;64cursor = String.fromCharCode(code);65}66cm.replaceRange(cursor, pos);67para = cm.getRange(from, to); // now it has the sentinel character in it.68}6970const words = split(para);71let formatted = "";72let k = 0;73for (const word of words) {74let next = (k > 0 ? " " : "") + word;75if (k > 0 && k + next.length > cols) {76formatted += "\n";77k = 0;78next = word;79}80formatted += next;81k += next.length;82}83cm.replaceRange(formatted, from, to);8485if (cursor != "") {86for (let line = from.line; line <= to.line; line++) {87const x = cm.getLine(line);88if (x == null) continue;89const ch = x.indexOf(cursor);90if (ch != -1) {91const before = x.slice(0, ch);92let after = x.slice(ch + cursor.length);93if (94is_white_space(after[0]) &&95is_white_space(before[before.length - 1])96) {97after = after.slice(1);98}99cm.replaceRange(100before + after,101{ line, ch: 0 },102{ line, ch: cm.getLine(line).length }103);104cm.setCursor({ ch, line });105break;106}107}108}109});110111112