Path: blob/master/src/packages/frontend/codemirror/extensions/sagews.ts
1496 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45// I think the extensions are all used to support Sage worksheets...67import * as CodeMirror from "codemirror";8import { defaults, required } from "@cocalc/util/misc";9import { IS_MOBILE } from "../../feature";10import $ from "jquery";1112// Apply a CodeMirror changeObj to this editing buffer.13CodeMirror.defineExtension("apply_changeObj", function (changeObj) {14// @ts-ignore15const editor = this;16editor.replaceRange(changeObj.text, changeObj.from, changeObj.to);17if (changeObj.next != null) {18return editor.apply_changeObj(changeObj.next);19}20});2122// This is an improved rewrite of simple-hint.js from the CodeMirror3 distribution.23// It is used only by sage worksheets and nothing else, currently.24CodeMirror.defineExtension(25"showCompletions",26function (opts: {27from: CodeMirror.Position;28to: CodeMirror.Position;29completions: string[];30target: string;31completions_size?: number;32}): void {33const { from, to, completions, target, completions_size } = defaults(opts, {34from: required,35to: required,36completions: required,37target: required,38completions_size: 20,39});4041if (completions.length === 0) {42return;43}4445// @ts-ignore46const editor = this;47const start_cursor_pos = editor.getCursor();48const insert = function (str: string): void {49const pos = editor.getCursor();50from.line = pos.line;51to.line = pos.line;52const shift = pos.ch - start_cursor_pos.ch;53from.ch += shift;54to.ch += shift;55editor.replaceRange(str, from, to);56};5758if (completions.length === 1) {59// do not include target in appended completion if it has a '*'60if (target.indexOf("*") === -1) {61insert(target + completions[0]);62} else {63insert(completions[0]);64}65return;66}6768const sel: any = $("<select>").css("width", "auto");69const complete = $("<div>").addClass("webapp-completions").append(sel);70for (let c of completions) {71// do not include target in appended completion if it has a '*'72if (target.indexOf("*") === -1) {73sel.append($("<option>").text(target + c));74} else {75sel.append($("<option>").text(c));76}77}78sel.find(":first").attr("selected", true);79sel.attr("size", Math.min(completions_size, completions.length));80const pos = editor.cursorCoords(from);8182complete.css({83left: pos.left + "px",84top: pos.bottom + "px",85});86$("body").append(complete);87// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.88const winW =89window.innerWidth ||90Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);91if (winW - pos.left < sel.attr("clientWidth")) {92complete.css({ left: pos.left - sel.attr("clientWidth") + "px" });93}94// Hide scrollbar95if (completions.length <= completions_size) {96complete.css({ width: sel.attr("clientWidth") - 1 + "px" });97}9899let done = false;100101const close = function () {102if (done) {103return;104}105done = true;106complete.remove();107};108109const pick = function () {110insert(sel.val());111close();112if (!IS_MOBILE) {113return setTimeout(() => editor.focus(), 50);114}115};116117sel.blur(pick);118sel.dblclick(pick);119if (!IS_MOBILE) {120// do not do this on mobile, since it makes it unusable!121sel.click(pick);122}123sel.keydown(function (event) {124const code = event.keyCode;125switch (code) {126case 13: // enter127pick();128return false;129case 27:130close();131editor.focus();132return false;133default:134if (135code !== 38 &&136code !== 40 &&137code !== 33 &&138code !== 34 &&139!(CodeMirror as any).isModifierKey(event)140) {141close();142editor.focus();143// Pass to CodeMirror (e.g., backspace)144return editor.triggerOnKeyDown(event);145}146}147});148sel.focus();149},150);151152function get_inspect_dialog(editor) {153const dialog = $(`\154<div class="webapp-codemirror-introspect modal"155data-backdrop="static" tabindex="-1" role="dialog" aria-hidden="true">156<div class="modal-dialog" style="width:90%">157<div class="modal-content">158<div class="modal-header">159<button type="button" class="close" aria-hidden="true">160<span style="font-size:20pt;">×</span>161</button>162<h4><div class="webapp-codemirror-introspect-title"></div></h4>163</div>164165<div class="webapp-codemirror-introspect-content-source-code cm-s-default">166</div>167<div class="webapp-codemirror-introspect-content-docstring cm-s-default">168</div>169170171<div class="modal-footer">172<button class="btn btn-close btn-default">Close</button>173</div>174</div>175</div>176</div>\177`);178// @ts-ignore179dialog.modal();180dialog.data("editor", editor);181182dialog.find("button").click(function () {183// @ts-ignore184dialog.modal("hide");185dialog.remove(); // also remove; we no longer have any use for this element!186});187188// see http://stackoverflow.com/questions/8363802/bind-a-function-to-twitter-bootstrap-modal-close189dialog.on("hidden.bs.modal", function () {190dialog.data("editor").focus?.();191dialog.data("editor", 0);192});193194return dialog;195}196197CodeMirror.defineExtension(198"showIntrospect",199function (opts: {200from: CodeMirror.Position;201content: string;202type: string;203target: string;204}): void {205opts = defaults(opts, {206from: required,207content: required,208type: required, // 'docstring', 'source-code' -- FUTURE:209target: required,210});211// @ts-ignore212const editor = this;213if (typeof opts.content !== "string") {214// If for some reason the content isn't a string (e.g., undefined or an object or something else),215// convert it a string, which will display fine.216opts.content = `${JSON.stringify(opts.content)}`;217}218const element = get_inspect_dialog(editor);219element.find(".webapp-codemirror-introspect-title").text(opts.target);220element.show();221let elt;222if (opts.type === "source-code") {223elt = element.find(224".webapp-codemirror-introspect-content-source-code",225)[0];226if (elt != null) {227// see https://github.com/sagemathinc/cocalc/issues/1993228(CodeMirror as any).runMode(opts.content, "python", elt);229}230} else {231elt = element.find(".webapp-codemirror-introspect-content-docstring")[0];232if (elt != null) {233// see https://github.com/sagemathinc/cocalc/issues/1993234(CodeMirror as any).runMode(opts.content, "text/x-rst", elt);235}236}237},238);239240241