| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- import { EditorView, basicSetup } from "codemirror";
- import { Compartment, EditorState, type Extension } from "@codemirror/state";
- import { lineNumbers, ViewUpdate } from "@codemirror/view";
- import { vim } from "@replit/codemirror-vim";
- import { json as cmJson } from "@codemirror/lang-json";
- import { html as cmHtml } from "@codemirror/lang-html";
- import { getSetting, setSetting } from "./settings.svelte";
- import ls from "./localstorage";
- import type { HttpResponseBody } from "./types";
- const jsonExt = cmJson();
- const htmlExt = cmHtml();
- const vimExtension = vim();
- const relativeLines = lineNumbersRelative();
- let vimEnabled: boolean = $state(ls.VIM_MODE.get());
- export const isVimEnabled = () => vimEnabled;
- const langConfig = new Compartment();
- const stateChangeListener = new Compartment();
- const vimConfig = new Compartment();
- const lineWrapConfig = new Compartment();
- const editorPadding = EditorView.theme({
- ".cm-content": {
- marginBottom: "4rem",
- },
- ".cm-scroller": {
- marginBottom: "2rem",
- },
- "@media (min-height: 900px)": {
- ".cm-content": {
- marginBottom: "3rem",
- },
- ".cm-scroller": {
- marginBottom: "1rem",
- },
- },
- "@media (min-height: 1200px)": {
- ".cm-content": {
- marginBottom: "2rem",
- },
- ".cm-scroller": {
- marginBottom: "0.5rem",
- },
- },
- });
- export function init(
- id: string,
- lineWrap: boolean,
- vimMode: boolean,
- type?: HttpResponseBody["type"],
- ): EditorView {
- const extensions = [
- basicSetup,
- editorPadding,
- stateChangeListener.of(EditorView.updateListener.of(() => {})),
- ];
- const langExtensions = [];
- if (type != null) {
- const lang = langExt(type);
- if (lang != null) {
- langExtensions.push(lang);
- }
- }
- extensions.push(langConfig.of(langExtensions));
- const vimExtensions = [];
- if (vimEnabled && vimMode) {
- vimExtensions.push(vimExtension, relativeLines);
- }
- extensions.push(vimConfig.of(vimExtensions));
- if (lineWrap) {
- extensions.push(lineWrapConfig.of([EditorView.lineWrapping]));
- } else {
- extensions.push(lineWrapConfig.of([]));
- }
- return new EditorView({
- parent: document.getElementById(id) ?? undefined,
- state: EditorState.create({ extensions }),
- });
- }
- export function clearContent(view: EditorView) {
- view.dispatch({
- changes: {
- from: 0,
- to: view.state.doc.length,
- insert: "",
- },
- });
- }
- export function setContent(
- view: EditorView,
- content?: string,
- type?: HttpResponseBody["type"],
- ) {
- if (type != null) {
- const lang = langExt(type);
- view.dispatch({
- effects: langConfig.reconfigure(lang != null ? [lang] : []),
- });
- }
- view.dispatch({
- changes: {
- from: 0,
- to: view.state.doc.length,
- insert: content ?? "",
- },
- });
- }
- export function setUpdateHandler(
- view: EditorView,
- fn: (update: ViewUpdate) => void,
- ) {
- view.dispatch({
- effects: stateChangeListener.reconfigure(EditorView.updateListener.of(fn)),
- });
- }
- export function toggleVim(view: EditorView) {
- vimEnabled = !vimEnabled;
- view.dispatch({
- effects: vimConfig.reconfigure(
- vimEnabled ? [vimExtension, relativeLines] : [],
- ),
- });
- ls.VIM_MODE.set(vimEnabled);
- }
- export function toggleWrap(view: EditorView, value: boolean) {
- view.dispatch({
- effects: lineWrapConfig.reconfigure(value ? [EditorView.lineWrapping] : []),
- });
- ls.WRAP_RESPONSE.set(value);
- }
- /** Copy the contents of the editor to the system clipboard. */
- export async function copyContent(view: EditorView) {
- await navigator.clipboard.writeText(view.state.doc.toString());
- }
- /** Parse and stringify the editor contents as JSON in pretty mode. */
- export function formatJson(view: EditorView) {
- try {
- view.dispatch({
- changes: {
- from: 0,
- to: view.state.doc.length,
- insert: JSON.stringify(JSON.parse(view.state.doc.toString()), null, 2),
- },
- });
- } catch (e) {
- console.warn(e);
- }
- }
- /**
- * Sets the gutter to display relative lines for VIM motions.
- */
- function lineNumbersRelative(): Extension {
- return [lineNumbers({ formatNumber: relativeLineNumbers })];
- }
- function relativeLineNumbers(lineNo: number, state: EditorState) {
- const cursorLine = state.doc.lineAt(
- state.selection.asSingle().ranges[0].to,
- ).number;
- // Absolute number for the current line
- if (lineNo === cursorLine) return lineNo.toString();
- // Relative number for all other lines
- return Math.abs(cursorLine - lineNo).toString();
- }
- function langExt(type: HttpResponseBody["type"]): Extension | null {
- switch (type) {
- case "TextPlain":
- return null;
- case "TextHtml": {
- return htmlExt;
- }
- case "Json": {
- return jsonExt;
- }
- }
- }
|