diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a06f5b..61fc363 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,18 +3,19 @@ All notable changes to Rich Foot will be documented in this file. -## [1.6.2] - 2024-11-23 +## [1.7.0] - 2024-11-26 +### ✨ Added +- `Custom Created/Modified Date Property` fields to allow users to specify their own frontmatter properties for dates, useful when file system dates are affected by sync processes and you track them separately. -### Updated -- outlinks section to inclued transclusion links (example `[[note#section]]` or `[text](note#section)`) +## [1.6.2] - 2024-11-23 +### 📦 Updated +- Outlinks section to inclued transclusion links (example `[[note#section]]` or `[text](note#section)`) ## [1.6.1] - 2024-11-14 - ### 🐛 Fixed - Fixed console error when switching between reading/editing modes ## [1.6.0] - 2024-11-09 - ### ✨ Added - New Border, Links, and Date color customization options in settings - Color picker to select custom colors @@ -22,12 +23,10 @@ All notable changes to Rich Foot will be documented in this file. - Real-time color updates ## [1.5.1] - 2024-10-31 - ### 🐛 Fixed - Fixed bug where excluded folders were not being saved correctly ## [1.5.0] - 2024-10-31 - ### ✨ Added - New Style Settings section in plugin settings - Border customization options: @@ -42,13 +41,12 @@ All notable changes to Rich Foot will be documented in this file. - Reset buttons for all customization options - Improved settings UI organization -### 📦 Changed +### 📦 Updated - Reorganized settings panel for better usability - Updated documentation to reflect new customization options - Improved CSS variable management for better theme compatibility ## [1.4.0] - 2023-10-28 - ### ✨ Added - Display `Outlinks` (under the `Backlinks` section) - Updated Settings page: @@ -56,29 +54,24 @@ All notable changes to Rich Foot will be documented in this file. - Improved Exclude Folders controls for easier management ## [1.3.3] - 2023-10-21 - ### 🐛 Fixed - Only create backlinks to notes, not files like images - Fixed issue with backlinks being removed/re-added when scrolling long notes - Fixed issue with rich-foot being displayed to the right of notes in editing mode ## [1.3.0] - 2023-10-17 - ### 🐛 Fixed - Compatibility issues with Obsidian v1.7.2+ that introduces `DeferredView` ## [1.2.0] - 2023-10-12 - -### 📦 Changed +### 🐛 Fixed - Don't create backlinks that reference themselves ## [1.1.0] - 2023-10-09 - ### ✨ Added - Support for `rich-foot` rendering in both `editing` and `live preview` modes ## [1.0.0] - 2023-09-23 - ### ✨ Added - Initial release - Backlinks displayed as tags in note footers diff --git a/UPDATE.md b/UPDATE.md index 8a76baf..6e30f41 100644 --- a/UPDATE.md +++ b/UPDATE.md @@ -1,18 +1,7 @@ -## 🎉 What's New +## 📆 Dates Your Way -### v1.6.2 -#### Updated -- outlinks section to inclued transclusion links (example `[[note#section]]` or `[text](note#section)`) +### v1.7.0 +#### ✨ Added +- `Custom Created/Modified Date Property` fields to allow users to specify their own frontmatter properties for dates, useful when file system dates are affected by sync processes and you track them separately. -### v1.6.1 -#### Fixed -- Fixed console error when switching between reading/editing modes - -### v1.6.0 -#### New Color Customization Options -- New Border, Links, and Date color customization options in settings - - Color picker to select custom colors - - Reset button to restore default colors (theme accent color) - - Real-time color updates - -![New Color Customization Options](https://raw.githubusercontent.com/jparkerweb/rich-foot/refs/heads/main/img/releases/rich-foot-v1.6.0.jpg) \ No newline at end of file +[![screenshot](https://raw.githubusercontent.com/jparkerweb/rich-foot/refs/heads/develop/img/releases/rich-foot-v1.7.0.jpg)](https://raw.githubusercontent.com/jparkerweb/rich-foot/refs/heads/develop/img/releases/rich-foot-v1.7.0.jpg) \ No newline at end of file diff --git a/example-vault.zip b/example-vault.zip index 6a6a27e..c3769c7 100644 Binary files a/example-vault.zip and b/example-vault.zip differ diff --git a/example-vault/rich-foot-example/.obsidian/appearance.json b/example-vault/rich-foot-example/.obsidian/appearance.json index 1c6a8bc..324cb42 100644 --- a/example-vault/rich-foot-example/.obsidian/appearance.json +++ b/example-vault/rich-foot-example/.obsidian/appearance.json @@ -1,3 +1,6 @@ { - "accentColor": "#f7a518" + "accentColor": "#f7a518", + "enabledCssSnippets": [ + "utility" + ] } \ No newline at end of file diff --git a/example-vault/rich-foot-example/.obsidian/community-plugins.json b/example-vault/rich-foot-example/.obsidian/community-plugins.json index 66cdbe1..69e1535 100644 --- a/example-vault/rich-foot-example/.obsidian/community-plugins.json +++ b/example-vault/rich-foot-example/.obsidian/community-plugins.json @@ -5,5 +5,8 @@ "open-plugin-settings", "obsidian-paste-image-rename", "url-into-selection", - "advanced-cursors" + "advanced-cursors", + "css-editor", + "obsidian-custom-frames", + "markdown-attributes" ] \ No newline at end of file diff --git a/example-vault/rich-foot-example/.obsidian/core-plugins.json b/example-vault/rich-foot-example/.obsidian/core-plugins.json index 1a36e94..5fcbb97 100644 --- a/example-vault/rich-foot-example/.obsidian/core-plugins.json +++ b/example-vault/rich-foot-example/.obsidian/core-plugins.json @@ -7,7 +7,7 @@ "canvas": false, "outgoing-link": false, "tag-pane": true, - "properties": false, + "properties": true, "page-preview": true, "daily-notes": false, "templates": true, diff --git a/example-vault/rich-foot-example/.obsidian/plugins/css-editor/main.js b/example-vault/rich-foot-example/.obsidian/plugins/css-editor/main.js new file mode 100644 index 0000000..234f1ad --- /dev/null +++ b/example-vault/rich-foot-example/.obsidian/plugins/css-editor/main.js @@ -0,0 +1,10475 @@ +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ + +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/main.ts +var main_exports = {}; +__export(main_exports, { + default: () => CssEditorPlugin +}); +module.exports = __toCommonJS(main_exports); +var import_obsidian7 = require("obsidian"); + +// src/views/CssEditorView.ts +var import_obsidian2 = require("obsidian"); +var import_view5 = require("@codemirror/view"); + +// node_modules/@replit/codemirror-vim/dist/index.js +var import_state = require("@codemirror/state"); +var import_language = require("@codemirror/language"); +var import_view = require("@codemirror/view"); +var import_search = require("@codemirror/search"); +var import_commands = require("@codemirror/commands"); +function initVim(CodeMirror2) { + var Pos2 = CodeMirror2.Pos; + function transformCursor(cm, range) { + var vim2 = cm.state.vim; + if (!vim2 || vim2.insertMode) + return range.head; + var head = vim2.sel.head; + if (!head) + return range.head; + if (vim2.visualBlock) { + if (range.head.line != head.line) { + return; + } + } + if (range.from() == range.anchor && !range.empty()) { + if (range.head.line == head.line && range.head.ch != head.ch) + return new Pos2(range.head.line, range.head.ch - 1); + } + return range.head; + } + function updateSelectionForSurrogateCharacters(cm, curStart, curEnd) { + if (curStart.line === curEnd.line && curStart.ch >= curEnd.ch - 1) { + var text = cm.getLine(curStart.line); + var charCode = text.charCodeAt(curStart.ch); + if (55296 <= charCode && charCode <= 55551) { + curEnd.ch += 1; + } + } + return { start: curStart, end: curEnd }; + } + var defaultKeymap2 = [ + // Key to key mapping. This goes first to make it possible to override + // existing mappings. + { keys: "", type: "keyToKey", toKeys: "h" }, + { keys: "", type: "keyToKey", toKeys: "l" }, + { keys: "", type: "keyToKey", toKeys: "k" }, + { keys: "", type: "keyToKey", toKeys: "j" }, + { keys: "g", type: "keyToKey", toKeys: "gk" }, + { keys: "g", type: "keyToKey", toKeys: "gj" }, + { keys: "", type: "keyToKey", toKeys: "l" }, + { keys: "", type: "keyToKey", toKeys: "h", context: "normal" }, + { keys: "", type: "keyToKey", toKeys: "x", context: "normal" }, + { keys: "", type: "keyToKey", toKeys: "W" }, + { keys: "", type: "keyToKey", toKeys: "B", context: "normal" }, + { keys: "", type: "keyToKey", toKeys: "w" }, + { keys: "", type: "keyToKey", toKeys: "b", context: "normal" }, + { keys: "", type: "keyToKey", toKeys: "j" }, + { keys: "", type: "keyToKey", toKeys: "k" }, + { keys: "", type: "keyToKey", toKeys: "" }, + { keys: "", type: "keyToKey", toKeys: "" }, + { keys: "", type: "keyToKey", toKeys: "", context: "insert" }, + { keys: "", type: "keyToKey", toKeys: "", context: "insert" }, + { keys: "", type: "keyToKey", toKeys: "" }, + // ipad keyboard sends C-Esc instead of C-[ + { keys: "", type: "keyToKey", toKeys: "", context: "insert" }, + { keys: "s", type: "keyToKey", toKeys: "cl", context: "normal" }, + { keys: "s", type: "keyToKey", toKeys: "c", context: "visual" }, + { keys: "S", type: "keyToKey", toKeys: "cc", context: "normal" }, + { keys: "S", type: "keyToKey", toKeys: "VdO", context: "visual" }, + { keys: "", type: "keyToKey", toKeys: "0" }, + { keys: "", type: "keyToKey", toKeys: "$" }, + { keys: "", type: "keyToKey", toKeys: "" }, + { keys: "", type: "keyToKey", toKeys: "" }, + { keys: "", type: "keyToKey", toKeys: "j^", context: "normal" }, + { keys: "", type: "keyToKey", toKeys: "i", context: "normal" }, + { keys: "", type: "action", action: "toggleOverwrite", context: "insert" }, + // Motions + { keys: "H", type: "motion", motion: "moveToTopLine", motionArgs: { linewise: true, toJumplist: true } }, + { keys: "M", type: "motion", motion: "moveToMiddleLine", motionArgs: { linewise: true, toJumplist: true } }, + { keys: "L", type: "motion", motion: "moveToBottomLine", motionArgs: { linewise: true, toJumplist: true } }, + { keys: "h", type: "motion", motion: "moveByCharacters", motionArgs: { forward: false } }, + { keys: "l", type: "motion", motion: "moveByCharacters", motionArgs: { forward: true } }, + { keys: "j", type: "motion", motion: "moveByLines", motionArgs: { forward: true, linewise: true } }, + { keys: "k", type: "motion", motion: "moveByLines", motionArgs: { forward: false, linewise: true } }, + { keys: "gj", type: "motion", motion: "moveByDisplayLines", motionArgs: { forward: true } }, + { keys: "gk", type: "motion", motion: "moveByDisplayLines", motionArgs: { forward: false } }, + { keys: "w", type: "motion", motion: "moveByWords", motionArgs: { forward: true, wordEnd: false } }, + { keys: "W", type: "motion", motion: "moveByWords", motionArgs: { forward: true, wordEnd: false, bigWord: true } }, + { keys: "e", type: "motion", motion: "moveByWords", motionArgs: { forward: true, wordEnd: true, inclusive: true } }, + { keys: "E", type: "motion", motion: "moveByWords", motionArgs: { forward: true, wordEnd: true, bigWord: true, inclusive: true } }, + { keys: "b", type: "motion", motion: "moveByWords", motionArgs: { forward: false, wordEnd: false } }, + { keys: "B", type: "motion", motion: "moveByWords", motionArgs: { forward: false, wordEnd: false, bigWord: true } }, + { keys: "ge", type: "motion", motion: "moveByWords", motionArgs: { forward: false, wordEnd: true, inclusive: true } }, + { keys: "gE", type: "motion", motion: "moveByWords", motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true } }, + { keys: "{", type: "motion", motion: "moveByParagraph", motionArgs: { forward: false, toJumplist: true } }, + { keys: "}", type: "motion", motion: "moveByParagraph", motionArgs: { forward: true, toJumplist: true } }, + { keys: "(", type: "motion", motion: "moveBySentence", motionArgs: { forward: false } }, + { keys: ")", type: "motion", motion: "moveBySentence", motionArgs: { forward: true } }, + { keys: "", type: "motion", motion: "moveByPage", motionArgs: { forward: true } }, + { keys: "", type: "motion", motion: "moveByPage", motionArgs: { forward: false } }, + { keys: "", type: "motion", motion: "moveByScroll", motionArgs: { forward: true, explicitRepeat: true } }, + { keys: "", type: "motion", motion: "moveByScroll", motionArgs: { forward: false, explicitRepeat: true } }, + { keys: "gg", type: "motion", motion: "moveToLineOrEdgeOfDocument", motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true } }, + { keys: "G", type: "motion", motion: "moveToLineOrEdgeOfDocument", motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true } }, + { keys: "g$", type: "motion", motion: "moveToEndOfDisplayLine" }, + { keys: "g^", type: "motion", motion: "moveToStartOfDisplayLine" }, + { keys: "g0", type: "motion", motion: "moveToStartOfDisplayLine" }, + { keys: "0", type: "motion", motion: "moveToStartOfLine" }, + { keys: "^", type: "motion", motion: "moveToFirstNonWhiteSpaceCharacter" }, + { keys: "+", type: "motion", motion: "moveByLines", motionArgs: { forward: true, toFirstChar: true } }, + { keys: "-", type: "motion", motion: "moveByLines", motionArgs: { forward: false, toFirstChar: true } }, + { keys: "_", type: "motion", motion: "moveByLines", motionArgs: { forward: true, toFirstChar: true, repeatOffset: -1 } }, + { keys: "$", type: "motion", motion: "moveToEol", motionArgs: { inclusive: true } }, + { keys: "%", type: "motion", motion: "moveToMatchedSymbol", motionArgs: { inclusive: true, toJumplist: true } }, + { keys: "f", type: "motion", motion: "moveToCharacter", motionArgs: { forward: true, inclusive: true } }, + { keys: "F", type: "motion", motion: "moveToCharacter", motionArgs: { forward: false } }, + { keys: "t", type: "motion", motion: "moveTillCharacter", motionArgs: { forward: true, inclusive: true } }, + { keys: "T", type: "motion", motion: "moveTillCharacter", motionArgs: { forward: false } }, + { keys: ";", type: "motion", motion: "repeatLastCharacterSearch", motionArgs: { forward: true } }, + { keys: ",", type: "motion", motion: "repeatLastCharacterSearch", motionArgs: { forward: false } }, + { keys: "'", type: "motion", motion: "goToMark", motionArgs: { toJumplist: true, linewise: true } }, + { keys: "`", type: "motion", motion: "goToMark", motionArgs: { toJumplist: true } }, + { keys: "]`", type: "motion", motion: "jumpToMark", motionArgs: { forward: true } }, + { keys: "[`", type: "motion", motion: "jumpToMark", motionArgs: { forward: false } }, + { keys: "]'", type: "motion", motion: "jumpToMark", motionArgs: { forward: true, linewise: true } }, + { keys: "['", type: "motion", motion: "jumpToMark", motionArgs: { forward: false, linewise: true } }, + // the next two aren't motions but must come before more general motion declarations + { keys: "]p", type: "action", action: "paste", isEdit: true, actionArgs: { after: true, isEdit: true, matchIndent: true } }, + { keys: "[p", type: "action", action: "paste", isEdit: true, actionArgs: { after: false, isEdit: true, matchIndent: true } }, + { keys: "]", type: "motion", motion: "moveToSymbol", motionArgs: { forward: true, toJumplist: true } }, + { keys: "[", type: "motion", motion: "moveToSymbol", motionArgs: { forward: false, toJumplist: true } }, + { keys: "|", type: "motion", motion: "moveToColumn" }, + { keys: "o", type: "motion", motion: "moveToOtherHighlightedEnd", context: "visual" }, + { keys: "O", type: "motion", motion: "moveToOtherHighlightedEnd", motionArgs: { sameLine: true }, context: "visual" }, + // Operators + { keys: "d", type: "operator", operator: "delete" }, + { keys: "y", type: "operator", operator: "yank" }, + { keys: "c", type: "operator", operator: "change" }, + { keys: "=", type: "operator", operator: "indentAuto" }, + { keys: ">", type: "operator", operator: "indent", operatorArgs: { indentRight: true } }, + { keys: "<", type: "operator", operator: "indent", operatorArgs: { indentRight: false } }, + { keys: "g~", type: "operator", operator: "changeCase" }, + { keys: "gu", type: "operator", operator: "changeCase", operatorArgs: { toLower: true }, isEdit: true }, + { keys: "gU", type: "operator", operator: "changeCase", operatorArgs: { toLower: false }, isEdit: true }, + { keys: "n", type: "motion", motion: "findNext", motionArgs: { forward: true, toJumplist: true } }, + { keys: "N", type: "motion", motion: "findNext", motionArgs: { forward: false, toJumplist: true } }, + { keys: "gn", type: "motion", motion: "findAndSelectNextInclusive", motionArgs: { forward: true } }, + { keys: "gN", type: "motion", motion: "findAndSelectNextInclusive", motionArgs: { forward: false } }, + // Operator-Motion dual commands + { keys: "x", type: "operatorMotion", operator: "delete", motion: "moveByCharacters", motionArgs: { forward: true }, operatorMotionArgs: { visualLine: false } }, + { keys: "X", type: "operatorMotion", operator: "delete", motion: "moveByCharacters", motionArgs: { forward: false }, operatorMotionArgs: { visualLine: true } }, + { keys: "D", type: "operatorMotion", operator: "delete", motion: "moveToEol", motionArgs: { inclusive: true }, context: "normal" }, + { keys: "D", type: "operator", operator: "delete", operatorArgs: { linewise: true }, context: "visual" }, + { keys: "Y", type: "operatorMotion", operator: "yank", motion: "expandToLine", motionArgs: { linewise: true }, context: "normal" }, + { keys: "Y", type: "operator", operator: "yank", operatorArgs: { linewise: true }, context: "visual" }, + { keys: "C", type: "operatorMotion", operator: "change", motion: "moveToEol", motionArgs: { inclusive: true }, context: "normal" }, + { keys: "C", type: "operator", operator: "change", operatorArgs: { linewise: true }, context: "visual" }, + { keys: "~", type: "operatorMotion", operator: "changeCase", motion: "moveByCharacters", motionArgs: { forward: true }, operatorArgs: { shouldMoveCursor: true }, context: "normal" }, + { keys: "~", type: "operator", operator: "changeCase", context: "visual" }, + { keys: "", type: "operatorMotion", operator: "delete", motion: "moveToStartOfLine", context: "insert" }, + { keys: "", type: "operatorMotion", operator: "delete", motion: "moveByWords", motionArgs: { forward: false, wordEnd: false }, context: "insert" }, + //ignore C-w in normal mode + { keys: "", type: "idle", context: "normal" }, + // Actions + { keys: "", type: "action", action: "jumpListWalk", actionArgs: { forward: true } }, + { keys: "", type: "action", action: "jumpListWalk", actionArgs: { forward: false } }, + { keys: "", type: "action", action: "scroll", actionArgs: { forward: true, linewise: true } }, + { keys: "", type: "action", action: "scroll", actionArgs: { forward: false, linewise: true } }, + { keys: "a", type: "action", action: "enterInsertMode", isEdit: true, actionArgs: { insertAt: "charAfter" }, context: "normal" }, + { keys: "A", type: "action", action: "enterInsertMode", isEdit: true, actionArgs: { insertAt: "eol" }, context: "normal" }, + { keys: "A", type: "action", action: "enterInsertMode", isEdit: true, actionArgs: { insertAt: "endOfSelectedArea" }, context: "visual" }, + { keys: "i", type: "action", action: "enterInsertMode", isEdit: true, actionArgs: { insertAt: "inplace" }, context: "normal" }, + { keys: "gi", type: "action", action: "enterInsertMode", isEdit: true, actionArgs: { insertAt: "lastEdit" }, context: "normal" }, + { keys: "I", type: "action", action: "enterInsertMode", isEdit: true, actionArgs: { insertAt: "firstNonBlank" }, context: "normal" }, + { keys: "gI", type: "action", action: "enterInsertMode", isEdit: true, actionArgs: { insertAt: "bol" }, context: "normal" }, + { keys: "I", type: "action", action: "enterInsertMode", isEdit: true, actionArgs: { insertAt: "startOfSelectedArea" }, context: "visual" }, + { keys: "o", type: "action", action: "newLineAndEnterInsertMode", isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: "normal" }, + { keys: "O", type: "action", action: "newLineAndEnterInsertMode", isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: "normal" }, + { keys: "v", type: "action", action: "toggleVisualMode" }, + { keys: "V", type: "action", action: "toggleVisualMode", actionArgs: { linewise: true } }, + { keys: "", type: "action", action: "toggleVisualMode", actionArgs: { blockwise: true } }, + { keys: "", type: "action", action: "toggleVisualMode", actionArgs: { blockwise: true } }, + { keys: "gv", type: "action", action: "reselectLastSelection" }, + { keys: "J", type: "action", action: "joinLines", isEdit: true }, + { keys: "gJ", type: "action", action: "joinLines", actionArgs: { keepSpaces: true }, isEdit: true }, + { keys: "p", type: "action", action: "paste", isEdit: true, actionArgs: { after: true, isEdit: true } }, + { keys: "P", type: "action", action: "paste", isEdit: true, actionArgs: { after: false, isEdit: true } }, + { keys: "r", type: "action", action: "replace", isEdit: true }, + { keys: "@", type: "action", action: "replayMacro" }, + { keys: "q", type: "action", action: "enterMacroRecordMode" }, + // Handle Replace-mode as a special case of insert mode. + { keys: "R", type: "action", action: "enterInsertMode", isEdit: true, actionArgs: { replace: true }, context: "normal" }, + { keys: "R", type: "operator", operator: "change", operatorArgs: { linewise: true, fullLine: true }, context: "visual", exitVisualBlock: true }, + { keys: "u", type: "action", action: "undo", context: "normal" }, + { keys: "u", type: "operator", operator: "changeCase", operatorArgs: { toLower: true }, context: "visual", isEdit: true }, + { keys: "U", type: "operator", operator: "changeCase", operatorArgs: { toLower: false }, context: "visual", isEdit: true }, + { keys: "", type: "action", action: "redo" }, + { keys: "m", type: "action", action: "setMark" }, + { keys: '"', type: "action", action: "setRegister" }, + { keys: "", type: "action", action: "insertRegister", context: "insert", isEdit: true }, + { keys: "", type: "action", action: "oneNormalCommand", context: "insert" }, + { keys: "zz", type: "action", action: "scrollToCursor", actionArgs: { position: "center" } }, + { keys: "z.", type: "action", action: "scrollToCursor", actionArgs: { position: "center" }, motion: "moveToFirstNonWhiteSpaceCharacter" }, + { keys: "zt", type: "action", action: "scrollToCursor", actionArgs: { position: "top" } }, + { keys: "z", type: "action", action: "scrollToCursor", actionArgs: { position: "top" }, motion: "moveToFirstNonWhiteSpaceCharacter" }, + { keys: "zb", type: "action", action: "scrollToCursor", actionArgs: { position: "bottom" } }, + { keys: "z-", type: "action", action: "scrollToCursor", actionArgs: { position: "bottom" }, motion: "moveToFirstNonWhiteSpaceCharacter" }, + { keys: ".", type: "action", action: "repeatLastEdit" }, + { keys: "", type: "action", action: "incrementNumberToken", isEdit: true, actionArgs: { increase: true, backtrack: false } }, + { keys: "", type: "action", action: "incrementNumberToken", isEdit: true, actionArgs: { increase: false, backtrack: false } }, + { keys: "", type: "action", action: "indent", actionArgs: { indentRight: true }, context: "insert" }, + { keys: "", type: "action", action: "indent", actionArgs: { indentRight: false }, context: "insert" }, + // Text object motions + { keys: "a", type: "motion", motion: "textObjectManipulation" }, + { keys: "i", type: "motion", motion: "textObjectManipulation", motionArgs: { textObjectInner: true } }, + // Search + { keys: "/", type: "search", searchArgs: { forward: true, querySrc: "prompt", toJumplist: true } }, + { keys: "?", type: "search", searchArgs: { forward: false, querySrc: "prompt", toJumplist: true } }, + { keys: "*", type: "search", searchArgs: { forward: true, querySrc: "wordUnderCursor", wholeWordOnly: true, toJumplist: true } }, + { keys: "#", type: "search", searchArgs: { forward: false, querySrc: "wordUnderCursor", wholeWordOnly: true, toJumplist: true } }, + { keys: "g*", type: "search", searchArgs: { forward: true, querySrc: "wordUnderCursor", toJumplist: true } }, + { keys: "g#", type: "search", searchArgs: { forward: false, querySrc: "wordUnderCursor", toJumplist: true } }, + // Ex command + { keys: ":", type: "ex" } + ]; + var defaultKeymapLength = defaultKeymap2.length; + var defaultExCommandMap = [ + { name: "colorscheme", shortName: "colo" }, + { name: "map" }, + { name: "imap", shortName: "im" }, + { name: "nmap", shortName: "nm" }, + { name: "vmap", shortName: "vm" }, + { name: "omap", shortName: "om" }, + { name: "noremap", shortName: "no" }, + { name: "nnoremap", shortName: "nn" }, + { name: "vnoremap", shortName: "vn" }, + { name: "inoremap", shortName: "ino" }, + { name: "onoremap", shortName: "ono" }, + { name: "unmap" }, + { name: "mapclear", shortName: "mapc" }, + { name: "nmapclear", shortName: "nmapc" }, + { name: "vmapclear", shortName: "vmapc" }, + { name: "imapclear", shortName: "imapc" }, + { name: "omapclear", shortName: "omapc" }, + { name: "write", shortName: "w" }, + { name: "undo", shortName: "u" }, + { name: "redo", shortName: "red" }, + { name: "set", shortName: "se" }, + { name: "setlocal", shortName: "setl" }, + { name: "setglobal", shortName: "setg" }, + { name: "sort", shortName: "sor" }, + { name: "substitute", shortName: "s", possiblyAsync: true }, + { name: "nohlsearch", shortName: "noh" }, + { name: "yank", shortName: "y" }, + { name: "delmarks", shortName: "delm" }, + { name: "registers", shortName: "reg", excludeFromCommandHistory: true }, + { name: "vglobal", shortName: "v" }, + { name: "delete", shortName: "d" }, + { name: "join", shortName: "j" }, + { name: "normal", shortName: "norm" }, + { name: "global", shortName: "g" } + ]; + function enterVimMode(cm) { + cm.setOption("disableInput", true); + cm.setOption("showCursorWhenSelecting", false); + CodeMirror2.signal(cm, "vim-mode-change", { mode: "normal" }); + cm.on("cursorActivity", onCursorActivity); + maybeInitVimState(cm); + CodeMirror2.on(cm.getInputField(), "paste", getOnPasteFn(cm)); + } + function leaveVimMode(cm) { + cm.setOption("disableInput", false); + cm.off("cursorActivity", onCursorActivity); + CodeMirror2.off(cm.getInputField(), "paste", getOnPasteFn(cm)); + cm.state.vim = null; + if (highlightTimeout) + clearTimeout(highlightTimeout); + } + function detachVimMap(cm, next) { + if (this == CodeMirror2.keyMap.vim) { + cm.options.$customCursor = null; + CodeMirror2.rmClass(cm.getWrapperElement(), "cm-fat-cursor"); + } + if (!next || next.attach != attachVimMap) + leaveVimMode(cm); + } + function attachVimMap(cm, prev) { + if (this == CodeMirror2.keyMap.vim) { + if (cm.curOp) + cm.curOp.selectionChanged = true; + cm.options.$customCursor = transformCursor; + CodeMirror2.addClass(cm.getWrapperElement(), "cm-fat-cursor"); + } + if (!prev || prev.attach != attachVimMap) + enterVimMode(cm); + } + CodeMirror2.defineOption("vimMode", false, function(cm, val, prev) { + if (val && cm.getOption("keyMap") != "vim") + cm.setOption("keyMap", "vim"); + else if (!val && prev != CodeMirror2.Init && /^vim/.test(cm.getOption("keyMap"))) + cm.setOption("keyMap", "default"); + }); + function cmKey(key, cm) { + if (!cm) { + return void 0; + } + if (this[key]) { + return this[key]; + } + var vimKey2 = cmKeyToVimKey(key); + if (!vimKey2) { + return false; + } + var cmd = vimApi.findKey(cm, vimKey2); + if (typeof cmd == "function") { + CodeMirror2.signal(cm, "vim-keypress", vimKey2); + } + return cmd; + } + var modifiers = { Shift: "S", Ctrl: "C", Alt: "A", Cmd: "D", Mod: "A", CapsLock: "" }; + var specialKeys = { Enter: "CR", Backspace: "BS", Delete: "Del", Insert: "Ins" }; + var vimToCmKeyMap = {}; + "Left|Right|Up|Down|End|Home".split("|").concat(Object.keys(specialKeys)).forEach(function(x) { + vimToCmKeyMap[(specialKeys[x] || "").toLowerCase()] = vimToCmKeyMap[x.toLowerCase()] = x; + }); + function cmKeyToVimKey(key) { + if (key.charAt(0) == "'") { + return key.charAt(1); + } + var pieces = key.split(/-(?!$)/); + var lastPiece = pieces[pieces.length - 1]; + if (pieces.length == 1 && pieces[0].length == 1) { + return false; + } else if (pieces.length == 2 && pieces[0] == "Shift" && lastPiece.length == 1) { + return false; + } + var hasCharacter = false; + for (var i = 0; i < pieces.length; i++) { + var piece = pieces[i]; + if (piece in modifiers) { + pieces[i] = modifiers[piece]; + } else { + hasCharacter = true; + } + if (piece in specialKeys) { + pieces[i] = specialKeys[piece]; + } + } + if (!hasCharacter) { + return false; + } + if (isUpperCase(lastPiece)) { + pieces[pieces.length - 1] = lastPiece.toLowerCase(); + } + return "<" + pieces.join("-") + ">"; + } + function getOnPasteFn(cm) { + var vim2 = cm.state.vim; + if (!vim2.onPasteFn) { + vim2.onPasteFn = function() { + if (!vim2.insertMode) { + cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); + actions.enterInsertMode(cm, {}, vim2); + } + }; + } + return vim2.onPasteFn; + } + var numberRegex = /[\d]/; + var wordCharTest = [CodeMirror2.isWordChar, function(ch) { + return ch && !CodeMirror2.isWordChar(ch) && !/\s/.test(ch); + }], bigWordCharTest = [function(ch) { + return /\S/.test(ch); + }]; + function makeKeyRange(start, size) { + var keys2 = []; + for (var i = start; i < start + size; i++) { + keys2.push(String.fromCharCode(i)); + } + return keys2; + } + var upperCaseAlphabet = makeKeyRange(65, 26); + var lowerCaseAlphabet = makeKeyRange(97, 26); + var numbers = makeKeyRange(48, 10); + var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ["<", ">"]); + var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ["-", '"', ".", ":", "_", "/", "+"]); + var upperCaseChars; + try { + upperCaseChars = new RegExp("^[\\p{Lu}]$", "u"); + } catch (_) { + upperCaseChars = /^[A-Z]$/; + } + function isLine(cm, line) { + return line >= cm.firstLine() && line <= cm.lastLine(); + } + function isLowerCase(k) { + return /^[a-z]$/.test(k); + } + function isMatchableSymbol(k) { + return "()[]{}".indexOf(k) != -1; + } + function isNumber(k) { + return numberRegex.test(k); + } + function isUpperCase(k) { + return upperCaseChars.test(k); + } + function isWhiteSpaceString(k) { + return /^\s*$/.test(k); + } + function isEndOfSentenceSymbol(k) { + return ".?!".indexOf(k) != -1; + } + function inArray(val, arr) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] == val) { + return true; + } + } + return false; + } + var options = {}; + function defineOption(name, defaultValue, type, aliases, callback) { + if (defaultValue === void 0 && !callback) { + throw Error("defaultValue is required unless callback is provided"); + } + if (!type) { + type = "string"; + } + options[name] = { + type, + defaultValue, + callback + }; + if (aliases) { + for (var i = 0; i < aliases.length; i++) { + options[aliases[i]] = options[name]; + } + } + if (defaultValue) { + setOption(name, defaultValue); + } + } + function setOption(name, value, cm, cfg) { + var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; + if (!option) { + return new Error("Unknown option: " + name); + } + if (option.type == "boolean") { + if (value && value !== true) { + return new Error("Invalid argument: " + name + "=" + value); + } else if (value !== false) { + value = true; + } + } + if (option.callback) { + if (scope !== "local") { + option.callback(value, void 0); + } + if (scope !== "global" && cm) { + option.callback(value, cm); + } + } else { + if (scope !== "local") { + option.value = option.type == "boolean" ? !!value : value; + } + if (scope !== "global" && cm) { + cm.state.vim.options[name] = { value }; + } + } + } + function getOption(name, cm, cfg) { + var option = options[name]; + cfg = cfg || {}; + var scope = cfg.scope; + if (!option) { + return new Error("Unknown option: " + name); + } + if (option.callback) { + var local = cm && option.callback(void 0, cm); + if (scope !== "global" && local !== void 0) { + return local; + } + if (scope !== "local") { + return option.callback(); + } + return; + } else { + var local = scope !== "global" && (cm && cm.state.vim.options[name]); + return (local || scope !== "local" && option || {}).value; + } + } + defineOption("filetype", void 0, "string", ["ft"], function(name, cm) { + if (cm === void 0) { + return; + } + if (name === void 0) { + var mode = cm.getOption("mode"); + return mode == "null" ? "" : mode; + } else { + var mode = name == "" ? "null" : name; + cm.setOption("mode", mode); + } + }); + var createCircularJumpList = function() { + var size = 100; + var pointer = -1; + var head = 0; + var tail = 0; + var buffer = new Array(size); + function add(cm, oldCur, newCur) { + var current = pointer % size; + var curMark = buffer[current]; + function useNextSlot(cursor) { + var next = ++pointer % size; + var trashMark = buffer[next]; + if (trashMark) { + trashMark.clear(); + } + buffer[next] = cm.setBookmark(cursor); + } + if (curMark) { + var markPos = curMark.find(); + if (markPos && !cursorEqual(markPos, oldCur)) { + useNextSlot(oldCur); + } + } else { + useNextSlot(oldCur); + } + useNextSlot(newCur); + head = pointer; + tail = pointer - size + 1; + if (tail < 0) { + tail = 0; + } + } + function move(cm, offset) { + pointer += offset; + if (pointer > head) { + pointer = head; + } else if (pointer < tail) { + pointer = tail; + } + var mark = buffer[(size + pointer) % size]; + if (mark && !mark.find()) { + var inc = offset > 0 ? 1 : -1; + var newCur; + var oldCur = cm.getCursor(); + do { + pointer += inc; + mark = buffer[(size + pointer) % size]; + if (mark && (newCur = mark.find()) && !cursorEqual(oldCur, newCur)) { + break; + } + } while (pointer < head && pointer > tail); + } + return mark; + } + function find(cm, offset) { + var oldPointer = pointer; + var mark = move(cm, offset); + pointer = oldPointer; + return mark && mark.find(); + } + return { + cachedCursor: void 0, + //used for # and * jumps + add, + find, + move + }; + }; + var createInsertModeChanges = function(c) { + if (c) { + return { + changes: c.changes, + expectCursorActivityForChange: c.expectCursorActivityForChange + }; + } + return { + // Change list + changes: [], + // Set to true on change, false on cursorActivity. + expectCursorActivityForChange: false + }; + }; + function MacroModeState() { + this.latestRegister = void 0; + this.isPlaying = false; + this.isRecording = false; + this.replaySearchQueries = []; + this.onRecordingDone = void 0; + this.lastInsertModeChanges = createInsertModeChanges(); + } + MacroModeState.prototype = { + exitMacroRecordMode: function() { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.onRecordingDone) { + macroModeState.onRecordingDone(); + } + macroModeState.onRecordingDone = void 0; + macroModeState.isRecording = false; + }, + enterMacroRecordMode: function(cm, registerName) { + var register = vimGlobalState.registerController.getRegister(registerName); + if (register) { + register.clear(); + this.latestRegister = registerName; + if (cm.openDialog) { + var template = dom("span", { class: "cm-vim-message" }, "recording @" + registerName); + this.onRecordingDone = cm.openDialog(template, null, { bottom: true }); + } + this.isRecording = true; + } + } + }; + function maybeInitVimState(cm) { + if (!cm.state.vim) { + cm.state.vim = { + inputState: new InputState(), + // Vim's input state that triggered the last edit, used to repeat + // motions and operators with '.'. + lastEditInputState: void 0, + // Vim's action command before the last edit, used to repeat actions + // with '.' and insert mode repeat. + lastEditActionCommand: void 0, + // When using jk for navigation, if you move from a longer line to a + // shorter line, the cursor may clip to the end of the shorter line. + // If j is pressed again and cursor goes to the next line, the + // cursor should go back to its horizontal position on the longer + // line if it can. This is to keep track of the horizontal position. + lastHPos: -1, + // Doing the same with screen-position for gj/gk + lastHSPos: -1, + // The last motion command run. Cleared if a non-motion command gets + // executed in between. + lastMotion: null, + marks: {}, + insertMode: false, + insertModeReturn: false, + // Repeat count for changes made in insert mode, triggered by key + // sequences like 3,i. Only exists when insertMode is true. + insertModeRepeat: void 0, + visualMode: false, + // If we are in visual line mode. No effect if visualMode is false. + visualLine: false, + visualBlock: false, + lastSelection: null, + lastPastedText: null, + sel: {}, + // Buffer-local/window-local values of vim options. + options: {} + }; + } + return cm.state.vim; + } + var vimGlobalState; + function resetVimGlobalState() { + vimGlobalState = { + // The current search query. + searchQuery: null, + // Whether we are searching backwards. + searchIsReversed: false, + // Replace part of the last substituted pattern + lastSubstituteReplacePart: void 0, + jumpList: createCircularJumpList(), + macroModeState: new MacroModeState(), + // Recording latest f, t, F or T motion command. + lastCharacterSearch: { increment: 0, forward: true, selectedCharacter: "" }, + registerController: new RegisterController({}), + // search history buffer + searchHistoryController: new HistoryController(), + // ex Command history buffer + exCommandHistoryController: new HistoryController() + }; + for (var optionName in options) { + var option = options[optionName]; + option.value = option.defaultValue; + } + } + var lastInsertModeKeyTimer; + var vimApi = { + enterVimMode, + leaveVimMode, + buildKeyMap: function() { + }, + // Testing hook, though it might be useful to expose the register + // controller anyway. + getRegisterController: function() { + return vimGlobalState.registerController; + }, + // Testing hook. + resetVimGlobalState_: resetVimGlobalState, + // Testing hook. + getVimGlobalState_: function() { + return vimGlobalState; + }, + // Testing hook. + maybeInitVimState_: maybeInitVimState, + suppressErrorLogging: false, + InsertModeKey, + map: function(lhs, rhs, ctx) { + exCommandDispatcher.map(lhs, rhs, ctx); + }, + unmap: function(lhs, ctx) { + return exCommandDispatcher.unmap(lhs, ctx); + }, + // Non-recursive map function. + // NOTE: This will not create mappings to key maps that aren't present + // in the default key map. See TODO at bottom of function. + noremap: function(lhs, rhs, ctx) { + exCommandDispatcher.map(lhs, rhs, ctx, true); + }, + // Remove all user-defined mappings for the provided context. + mapclear: function(ctx) { + var actualLength = defaultKeymap2.length, origLength = defaultKeymapLength; + var userKeymap = defaultKeymap2.slice(0, actualLength - origLength); + defaultKeymap2 = defaultKeymap2.slice(actualLength - origLength); + if (ctx) { + for (var i = userKeymap.length - 1; i >= 0; i--) { + var mapping = userKeymap[i]; + if (ctx !== mapping.context) { + if (mapping.context) { + this._mapCommand(mapping); + } else { + var contexts = ["normal", "insert", "visual"]; + for (var j in contexts) { + if (contexts[j] !== ctx) { + var newMapping = {}; + for (var key in mapping) { + newMapping[key] = mapping[key]; + } + newMapping.context = contexts[j]; + this._mapCommand(newMapping); + } + } + } + } + } + } + }, + // TODO: Expose setOption and getOption as instance methods. Need to decide how to namespace + // them, or somehow make them work with the existing CodeMirror setOption/getOption API. + setOption, + getOption, + defineOption, + defineEx: function(name, prefix, func) { + if (!prefix) { + prefix = name; + } else if (name.indexOf(prefix) !== 0) { + throw new Error('(Vim.defineEx) "' + prefix + '" is not a prefix of "' + name + '", command not registered'); + } + exCommands[name] = func; + exCommandDispatcher.commandMap_[prefix] = { name, shortName: prefix, type: "api" }; + }, + handleKey: function(cm, key, origin) { + var command = this.findKey(cm, key, origin); + if (typeof command === "function") { + return command(); + } + }, + multiSelectHandleKey, + /** + * This is the outermost function called by CodeMirror, after keys have + * been mapped to their Vim equivalents. + * + * Finds a command based on the key (and cached keys if there is a + * multi-key sequence). Returns `undefined` if no key is matched, a noop + * function if a partial match is found (multi-key), and a function to + * execute the bound command if a a key is matched. The function always + * returns true. + */ + findKey: function(cm, key, origin) { + var vim2 = maybeInitVimState(cm); + function handleMacroRecording() { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isRecording) { + if (key == "q") { + macroModeState.exitMacroRecordMode(); + clearInputState(cm); + return true; + } + if (origin != "mapping") { + logKey(macroModeState, key); + } + } + } + function handleEsc() { + if (key == "") { + if (vim2.visualMode) { + exitVisualMode(cm); + } else if (vim2.insertMode) { + exitInsertMode(cm); + } else { + return; + } + clearInputState(cm); + return true; + } + } + function handleKeyInsertMode() { + if (handleEsc()) { + return true; + } + vim2.inputState.keyBuffer.push(key); + var keys2 = vim2.inputState.keyBuffer.join(""); + var keysAreChars = key.length == 1; + var match = commandDispatcher.matchCommand(keys2, defaultKeymap2, vim2.inputState, "insert"); + var changeQueue = vim2.inputState.changeQueue; + if (match.type == "none") { + clearInputState(cm); + return false; + } else if (match.type == "partial") { + if (lastInsertModeKeyTimer) { + window.clearTimeout(lastInsertModeKeyTimer); + } + lastInsertModeKeyTimer = keysAreChars && window.setTimeout( + function() { + if (vim2.insertMode && vim2.inputState.keyBuffer.length) { + clearInputState(cm); + } + }, + getOption("insertModeEscKeysTimeout") + ); + if (keysAreChars) { + var selections = cm.listSelections(); + if (!changeQueue || changeQueue.removed.length != selections.length) + changeQueue = vim2.inputState.changeQueue = new ChangeQueue(); + changeQueue.inserted += key; + for (var i = 0; i < selections.length; i++) { + var from = cursorMin(selections[i].anchor, selections[i].head); + var to = cursorMax(selections[i].anchor, selections[i].head); + var text = cm.getRange(from, cm.state.overwrite ? offsetCursor(to, 0, 1) : to); + changeQueue.removed[i] = (changeQueue.removed[i] || "") + text; + } + } + return !keysAreChars; + } + if (lastInsertModeKeyTimer) { + window.clearTimeout(lastInsertModeKeyTimer); + } + if (match.command && changeQueue) { + var selections = cm.listSelections(); + for (var i = 0; i < selections.length; i++) { + var here = selections[i].head; + cm.replaceRange( + changeQueue.removed[i] || "", + offsetCursor(here, 0, -changeQueue.inserted.length), + here, + "+input" + ); + } + vimGlobalState.macroModeState.lastInsertModeChanges.changes.pop(); + } + if (!match.command) + clearInputState(cm); + return match.command; + } + function handleKeyNonInsertMode() { + if (handleMacroRecording() || handleEsc()) { + return true; + } + vim2.inputState.keyBuffer.push(key); + var keys2 = vim2.inputState.keyBuffer.join(""); + if (/^[1-9]\d*$/.test(keys2)) { + return true; + } + var keysMatcher = /^(\d*)(.*)$/.exec(keys2); + if (!keysMatcher) { + clearInputState(cm); + return false; + } + var context = vim2.visualMode ? "visual" : "normal"; + var mainKey = keysMatcher[2] || keysMatcher[1]; + if (vim2.inputState.operatorShortcut && vim2.inputState.operatorShortcut.slice(-1) == mainKey) { + mainKey = vim2.inputState.operatorShortcut; + } + var match = commandDispatcher.matchCommand(mainKey, defaultKeymap2, vim2.inputState, context); + if (match.type == "none") { + clearInputState(cm); + return false; + } else if (match.type == "partial") { + return true; + } else if (match.type == "clear") { + clearInputState(cm); + return true; + } + vim2.inputState.keyBuffer.length = 0; + keysMatcher = /^(\d*)(.*)$/.exec(keys2); + if (keysMatcher[1] && keysMatcher[1] != "0") { + vim2.inputState.pushRepeatDigit(keysMatcher[1]); + } + return match.command; + } + var command; + if (vim2.insertMode) { + command = handleKeyInsertMode(); + } else { + command = handleKeyNonInsertMode(); + } + if (command === false) { + return !vim2.insertMode && key.length === 1 ? function() { + return true; + } : void 0; + } else if (command === true) { + return function() { + return true; + }; + } else { + return function() { + return cm.operation(function() { + cm.curOp.isVimOp = true; + try { + if (command.type == "keyToKey") { + doKeyToKey(cm, command.toKeys, command); + } else { + commandDispatcher.processCommand(cm, vim2, command); + } + } catch (e) { + cm.state.vim = void 0; + maybeInitVimState(cm); + if (!vimApi.suppressErrorLogging) { + console["log"](e); + } + throw e; + } + return true; + }); + }; + } + }, + handleEx: function(cm, input) { + exCommandDispatcher.processCommand(cm, input); + }, + defineMotion, + defineAction, + defineOperator, + mapCommand, + _mapCommand, + defineRegister, + exitVisualMode, + exitInsertMode + }; + var keyToKeyStack = []; + var noremap = false; + function doKeyToKey(cm, keys2, fromKey) { + if (fromKey) { + if (keyToKeyStack.indexOf(fromKey) != -1) + return; + keyToKeyStack.push(fromKey); + noremap = fromKey.noremap; + } + try { + var vim2 = maybeInitVimState(cm); + var keyRe = /<(?:[CSMA]-)*\w+>|./gi; + var match; + while (match = keyRe.exec(keys2)) { + var key = match[0]; + var wasInsert = vim2.insertMode; + var result = vimApi.handleKey(cm, key, "mapping"); + if (!result && wasInsert && vim2.insertMode) { + if (key[0] == "<") { + var lowerKey = key.toLowerCase().slice(1, -1); + var parts = lowerKey.split("-"); + var lowerKey = parts.pop(); + if (lowerKey == "lt") + key = "<"; + else if (lowerKey == "space") + key = " "; + else if (lowerKey == "cr") + key = "\n"; + else if (vimToCmKeyMap.hasOwnProperty(lowerKey)) { + key = vimToCmKeyMap[lowerKey]; + sendCmKey(cm, key); + continue; + } else { + key = key[0]; + keyRe.lastIndex = match.index + 1; + } + } + cm.replaceSelection(key); + } + } + } finally { + noremap = false; + keyToKeyStack.length = 0; + } + } + function InputState() { + this.prefixRepeat = []; + this.motionRepeat = []; + this.operator = null; + this.operatorArgs = null; + this.motion = null; + this.motionArgs = null; + this.keyBuffer = []; + this.registerName = null; + this.changeQueue = null; + } + InputState.prototype.pushRepeatDigit = function(n) { + if (!this.operator) { + this.prefixRepeat = this.prefixRepeat.concat(n); + } else { + this.motionRepeat = this.motionRepeat.concat(n); + } + }; + InputState.prototype.getRepeat = function() { + var repeat = 0; + if (this.prefixRepeat.length > 0 || this.motionRepeat.length > 0) { + repeat = 1; + if (this.prefixRepeat.length > 0) { + repeat *= parseInt(this.prefixRepeat.join(""), 10); + } + if (this.motionRepeat.length > 0) { + repeat *= parseInt(this.motionRepeat.join(""), 10); + } + } + return repeat; + }; + function clearInputState(cm, reason) { + cm.state.vim.inputState = new InputState(); + CodeMirror2.signal(cm, "vim-command-done", reason); + } + function ChangeQueue() { + this.removed = []; + this.inserted = ""; + } + function Register(text, linewise, blockwise) { + this.clear(); + this.keyBuffer = [text || ""]; + this.insertModeChanges = []; + this.searchQueries = []; + this.linewise = !!linewise; + this.blockwise = !!blockwise; + } + Register.prototype = { + setText: function(text, linewise, blockwise) { + this.keyBuffer = [text || ""]; + this.linewise = !!linewise; + this.blockwise = !!blockwise; + }, + pushText: function(text, linewise) { + if (linewise) { + if (!this.linewise) { + this.keyBuffer.push("\n"); + } + this.linewise = true; + } + this.keyBuffer.push(text); + }, + pushInsertModeChanges: function(changes) { + this.insertModeChanges.push(createInsertModeChanges(changes)); + }, + pushSearchQuery: function(query) { + this.searchQueries.push(query); + }, + clear: function() { + this.keyBuffer = []; + this.insertModeChanges = []; + this.searchQueries = []; + this.linewise = false; + }, + toString: function() { + return this.keyBuffer.join(""); + } + }; + function defineRegister(name, register) { + var registers = vimGlobalState.registerController.registers; + if (!name || name.length != 1) { + throw Error("Register name must be 1 character"); + } + if (registers[name]) { + throw Error("Register already defined " + name); + } + registers[name] = register; + validRegisters.push(name); + } + function RegisterController(registers) { + this.registers = registers; + this.unnamedRegister = registers['"'] = new Register(); + registers["."] = new Register(); + registers[":"] = new Register(); + registers["/"] = new Register(); + registers["+"] = new Register(); + } + RegisterController.prototype = { + pushText: function(registerName, operator, text, linewise, blockwise) { + if (registerName === "_") + return; + if (linewise && text.charAt(text.length - 1) !== "\n") { + text += "\n"; + } + var register = this.isValidRegister(registerName) ? this.getRegister(registerName) : null; + if (!register) { + switch (operator) { + case "yank": + this.registers["0"] = new Register(text, linewise, blockwise); + break; + case "delete": + case "change": + if (text.indexOf("\n") == -1) { + this.registers["-"] = new Register(text, linewise); + } else { + this.shiftNumericRegisters_(); + this.registers["1"] = new Register(text, linewise); + } + break; + } + this.unnamedRegister.setText(text, linewise, blockwise); + return; + } + var append = isUpperCase(registerName); + if (append) { + register.pushText(text, linewise); + } else { + register.setText(text, linewise, blockwise); + } + if (registerName === "+") { + navigator.clipboard.writeText(text); + } + this.unnamedRegister.setText(register.toString(), linewise); + }, + // Gets the register named @name. If one of @name doesn't already exist, + // create it. If @name is invalid, return the unnamedRegister. + getRegister: function(name) { + if (!this.isValidRegister(name)) { + return this.unnamedRegister; + } + name = name.toLowerCase(); + if (!this.registers[name]) { + this.registers[name] = new Register(); + } + return this.registers[name]; + }, + isValidRegister: function(name) { + return name && inArray(name, validRegisters); + }, + shiftNumericRegisters_: function() { + for (var i = 9; i >= 2; i--) { + this.registers[i] = this.getRegister("" + (i - 1)); + } + } + }; + function HistoryController() { + this.historyBuffer = []; + this.iterator = 0; + this.initialPrefix = null; + } + HistoryController.prototype = { + // the input argument here acts a user entered prefix for a small time + // until we start autocompletion in which case it is the autocompleted. + nextMatch: function(input, up) { + var historyBuffer = this.historyBuffer; + var dir = up ? -1 : 1; + if (this.initialPrefix === null) + this.initialPrefix = input; + for (var i = this.iterator + dir; up ? i >= 0 : i < historyBuffer.length; i += dir) { + var element = historyBuffer[i]; + for (var j = 0; j <= element.length; j++) { + if (this.initialPrefix == element.substring(0, j)) { + this.iterator = i; + return element; + } + } + } + if (i >= historyBuffer.length) { + this.iterator = historyBuffer.length; + return this.initialPrefix; + } + if (i < 0) + return input; + }, + pushInput: function(input) { + var index = this.historyBuffer.indexOf(input); + if (index > -1) + this.historyBuffer.splice(index, 1); + if (input.length) + this.historyBuffer.push(input); + }, + reset: function() { + this.initialPrefix = null; + this.iterator = this.historyBuffer.length; + } + }; + var commandDispatcher = { + matchCommand: function(keys2, keyMap, inputState, context) { + var matches = commandMatches(keys2, keyMap, context, inputState); + if (!matches.full && !matches.partial) { + return { type: "none" }; + } else if (!matches.full && matches.partial) { + return { type: "partial" }; + } + var bestMatch; + for (var i = 0; i < matches.full.length; i++) { + var match = matches.full[i]; + if (!bestMatch) { + bestMatch = match; + } + } + if (bestMatch.keys.slice(-11) == "") { + var character = lastChar(keys2); + if (!character || character.length > 1) + return { type: "clear" }; + inputState.selectedCharacter = character; + } + return { type: "full", command: bestMatch }; + }, + processCommand: function(cm, vim2, command) { + vim2.inputState.repeatOverride = command.repeatOverride; + switch (command.type) { + case "motion": + this.processMotion(cm, vim2, command); + break; + case "operator": + this.processOperator(cm, vim2, command); + break; + case "operatorMotion": + this.processOperatorMotion(cm, vim2, command); + break; + case "action": + this.processAction(cm, vim2, command); + break; + case "search": + this.processSearch(cm, vim2, command); + break; + case "ex": + case "keyToEx": + this.processEx(cm, vim2, command); + break; + } + }, + processMotion: function(cm, vim2, command) { + vim2.inputState.motion = command.motion; + vim2.inputState.motionArgs = copyArgs(command.motionArgs); + this.evalInput(cm, vim2); + }, + processOperator: function(cm, vim2, command) { + var inputState = vim2.inputState; + if (inputState.operator) { + if (inputState.operator == command.operator) { + inputState.motion = "expandToLine"; + inputState.motionArgs = { linewise: true }; + this.evalInput(cm, vim2); + return; + } else { + clearInputState(cm); + } + } + inputState.operator = command.operator; + inputState.operatorArgs = copyArgs(command.operatorArgs); + if (command.keys.length > 1) { + inputState.operatorShortcut = command.keys; + } + if (command.exitVisualBlock) { + vim2.visualBlock = false; + updateCmSelection(cm); + } + if (vim2.visualMode) { + this.evalInput(cm, vim2); + } + }, + processOperatorMotion: function(cm, vim2, command) { + var visualMode = vim2.visualMode; + var operatorMotionArgs = copyArgs(command.operatorMotionArgs); + if (operatorMotionArgs) { + if (visualMode && operatorMotionArgs.visualLine) { + vim2.visualLine = true; + } + } + this.processOperator(cm, vim2, command); + if (!visualMode) { + this.processMotion(cm, vim2, command); + } + }, + processAction: function(cm, vim2, command) { + var inputState = vim2.inputState; + var repeat = inputState.getRepeat(); + var repeatIsExplicit = !!repeat; + var actionArgs = copyArgs(command.actionArgs) || {}; + if (inputState.selectedCharacter) { + actionArgs.selectedCharacter = inputState.selectedCharacter; + } + if (command.operator) { + this.processOperator(cm, vim2, command); + } + if (command.motion) { + this.processMotion(cm, vim2, command); + } + if (command.motion || command.operator) { + this.evalInput(cm, vim2); + } + actionArgs.repeat = repeat || 1; + actionArgs.repeatIsExplicit = repeatIsExplicit; + actionArgs.registerName = inputState.registerName; + clearInputState(cm); + vim2.lastMotion = null; + if (command.isEdit) { + this.recordLastEdit(vim2, inputState, command); + } + actions[command.action](cm, actionArgs, vim2); + }, + processSearch: function(cm, vim2, command) { + if (!cm.getSearchCursor) { + return; + } + var forward = command.searchArgs.forward; + var wholeWordOnly = command.searchArgs.wholeWordOnly; + getSearchState(cm).setReversed(!forward); + var promptPrefix = forward ? "/" : "?"; + var originalQuery = getSearchState(cm).getQuery(); + var originalScrollPos = cm.getScrollInfo(); + function handleQuery(query2, ignoreCase, smartCase) { + vimGlobalState.searchHistoryController.pushInput(query2); + vimGlobalState.searchHistoryController.reset(); + try { + updateSearchQuery(cm, query2, ignoreCase, smartCase); + } catch (e) { + showConfirm(cm, "Invalid regex: " + query2); + clearInputState(cm); + return; + } + commandDispatcher.processMotion(cm, vim2, { + type: "motion", + motion: "findNext", + motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist } + }); + } + function onPromptClose(query2) { + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + handleQuery( + query2, + true, + true + /** smartCase */ + ); + var macroModeState2 = vimGlobalState.macroModeState; + if (macroModeState2.isRecording) { + logSearchQuery(macroModeState2, query2); + } + } + function onPromptKeyUp(e, query2, close) { + var keyName = CodeMirror2.keyName(e), up, offset; + if (keyName == "Up" || keyName == "Down") { + up = keyName == "Up" ? true : false; + offset = e.target ? e.target.selectionEnd : 0; + query2 = vimGlobalState.searchHistoryController.nextMatch(query2, up) || ""; + close(query2); + if (offset && e.target) + e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length); + } else { + if (keyName != "Left" && keyName != "Right" && keyName != "Ctrl" && keyName != "Alt" && keyName != "Shift") + vimGlobalState.searchHistoryController.reset(); + } + var parsedQuery; + try { + parsedQuery = updateSearchQuery( + cm, + query2, + true, + true + /** smartCase */ + ); + } catch (e2) { + } + if (parsedQuery) { + cm.scrollIntoView(findNext(cm, !forward, parsedQuery), 30); + } else { + clearSearchHighlight(cm); + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + } + } + function onPromptKeyDown(e, query2, close) { + var keyName = CodeMirror2.keyName(e); + if (keyName == "Esc" || keyName == "Ctrl-C" || keyName == "Ctrl-[" || keyName == "Backspace" && query2 == "") { + vimGlobalState.searchHistoryController.pushInput(query2); + vimGlobalState.searchHistoryController.reset(); + updateSearchQuery(cm, originalQuery); + clearSearchHighlight(cm); + cm.scrollTo(originalScrollPos.left, originalScrollPos.top); + CodeMirror2.e_stop(e); + clearInputState(cm); + close(); + cm.focus(); + } else if (keyName == "Up" || keyName == "Down") { + CodeMirror2.e_stop(e); + } else if (keyName == "Ctrl-U") { + CodeMirror2.e_stop(e); + close(""); + } + } + switch (command.searchArgs.querySrc) { + case "prompt": + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { + var query = macroModeState.replaySearchQueries.shift(); + handleQuery( + query, + true, + false + /** smartCase */ + ); + } else { + showPrompt(cm, { + onClose: onPromptClose, + prefix: promptPrefix, + desc: "(JavaScript regexp)", + onKeyUp: onPromptKeyUp, + onKeyDown: onPromptKeyDown + }); + } + break; + case "wordUnderCursor": + var word = expandWordUnderCursor( + cm, + false, + false, + false, + true + /** noSymbol */ + ); + var isKeyword = true; + if (!word) { + word = expandWordUnderCursor( + cm, + false, + false, + false, + false + /** noSymbol */ + ); + isKeyword = false; + } + if (!word) { + return; + } + var query = cm.getLine(word.start.line).substring( + word.start.ch, + word.end.ch + ); + if (isKeyword && wholeWordOnly) { + query = "\\b" + query + "\\b"; + } else { + query = escapeRegex(query); + } + vimGlobalState.jumpList.cachedCursor = cm.getCursor(); + cm.setCursor(word.start); + handleQuery( + query, + true, + false + /** smartCase */ + ); + break; + } + }, + processEx: function(cm, vim2, command) { + function onPromptClose(input) { + vimGlobalState.exCommandHistoryController.pushInput(input); + vimGlobalState.exCommandHistoryController.reset(); + exCommandDispatcher.processCommand(cm, input); + if (cm.state.vim) + clearInputState(cm); + } + function onPromptKeyDown(e, input, close) { + var keyName = CodeMirror2.keyName(e), up, offset; + if (keyName == "Esc" || keyName == "Ctrl-C" || keyName == "Ctrl-[" || keyName == "Backspace" && input == "") { + vimGlobalState.exCommandHistoryController.pushInput(input); + vimGlobalState.exCommandHistoryController.reset(); + CodeMirror2.e_stop(e); + clearInputState(cm); + close(); + cm.focus(); + } + if (keyName == "Up" || keyName == "Down") { + CodeMirror2.e_stop(e); + up = keyName == "Up" ? true : false; + offset = e.target ? e.target.selectionEnd : 0; + input = vimGlobalState.exCommandHistoryController.nextMatch(input, up) || ""; + close(input); + if (offset && e.target) + e.target.selectionEnd = e.target.selectionStart = Math.min(offset, e.target.value.length); + } else if (keyName == "Ctrl-U") { + CodeMirror2.e_stop(e); + close(""); + } else { + if (keyName != "Left" && keyName != "Right" && keyName != "Ctrl" && keyName != "Alt" && keyName != "Shift") + vimGlobalState.exCommandHistoryController.reset(); + } + } + if (command.type == "keyToEx") { + exCommandDispatcher.processCommand(cm, command.exArgs.input); + } else { + if (vim2.visualMode) { + showPrompt(cm, { + onClose: onPromptClose, + prefix: ":", + value: "'<,'>", + onKeyDown: onPromptKeyDown, + selectValueOnOpen: false + }); + } else { + showPrompt(cm, { + onClose: onPromptClose, + prefix: ":", + onKeyDown: onPromptKeyDown + }); + } + } + }, + evalInput: function(cm, vim2) { + var inputState = vim2.inputState; + var motion = inputState.motion; + var motionArgs = inputState.motionArgs || {}; + var operator = inputState.operator; + var operatorArgs = inputState.operatorArgs || {}; + var registerName = inputState.registerName; + var sel = vim2.sel; + var origHead = copyCursor(vim2.visualMode ? clipCursorToContent(cm, sel.head) : cm.getCursor("head")); + var origAnchor = copyCursor(vim2.visualMode ? clipCursorToContent(cm, sel.anchor) : cm.getCursor("anchor")); + var oldHead = copyCursor(origHead); + var oldAnchor = copyCursor(origAnchor); + var newHead, newAnchor; + var repeat; + if (operator) { + this.recordLastEdit(vim2, inputState); + } + if (inputState.repeatOverride !== void 0) { + repeat = inputState.repeatOverride; + } else { + repeat = inputState.getRepeat(); + } + if (repeat > 0 && motionArgs.explicitRepeat) { + motionArgs.repeatIsExplicit = true; + } else if (motionArgs.noRepeat || !motionArgs.explicitRepeat && repeat === 0) { + repeat = 1; + motionArgs.repeatIsExplicit = false; + } + if (inputState.selectedCharacter) { + motionArgs.selectedCharacter = operatorArgs.selectedCharacter = inputState.selectedCharacter; + } + motionArgs.repeat = repeat; + clearInputState(cm); + if (motion) { + var motionResult = motions[motion](cm, origHead, motionArgs, vim2, inputState); + vim2.lastMotion = motions[motion]; + if (!motionResult) { + return; + } + if (motionArgs.toJumplist) { + var jumpList = vimGlobalState.jumpList; + var cachedCursor = jumpList.cachedCursor; + if (cachedCursor) { + recordJumpPosition(cm, cachedCursor, motionResult); + delete jumpList.cachedCursor; + } else { + recordJumpPosition(cm, origHead, motionResult); + } + } + if (motionResult instanceof Array) { + newAnchor = motionResult[0]; + newHead = motionResult[1]; + } else { + newHead = motionResult; + } + if (!newHead) { + newHead = copyCursor(origHead); + } + if (vim2.visualMode) { + if (!(vim2.visualBlock && newHead.ch === Infinity)) { + newHead = clipCursorToContent(cm, newHead, oldHead); + } + if (newAnchor) { + newAnchor = clipCursorToContent(cm, newAnchor); + } + newAnchor = newAnchor || oldAnchor; + sel.anchor = newAnchor; + sel.head = newHead; + updateCmSelection(cm); + updateMark( + cm, + vim2, + "<", + cursorIsBefore(newAnchor, newHead) ? newAnchor : newHead + ); + updateMark( + cm, + vim2, + ">", + cursorIsBefore(newAnchor, newHead) ? newHead : newAnchor + ); + } else if (!operator) { + newHead = clipCursorToContent(cm, newHead, oldHead); + cm.setCursor(newHead.line, newHead.ch); + } + } + if (operator) { + if (operatorArgs.lastSel) { + newAnchor = oldAnchor; + var lastSel = operatorArgs.lastSel; + var lineOffset = Math.abs(lastSel.head.line - lastSel.anchor.line); + var chOffset = Math.abs(lastSel.head.ch - lastSel.anchor.ch); + if (lastSel.visualLine) { + newHead = new Pos2(oldAnchor.line + lineOffset, oldAnchor.ch); + } else if (lastSel.visualBlock) { + newHead = new Pos2(oldAnchor.line + lineOffset, oldAnchor.ch + chOffset); + } else if (lastSel.head.line == lastSel.anchor.line) { + newHead = new Pos2(oldAnchor.line, oldAnchor.ch + chOffset); + } else { + newHead = new Pos2(oldAnchor.line + lineOffset, oldAnchor.ch); + } + vim2.visualMode = true; + vim2.visualLine = lastSel.visualLine; + vim2.visualBlock = lastSel.visualBlock; + sel = vim2.sel = { + anchor: newAnchor, + head: newHead + }; + updateCmSelection(cm); + } else if (vim2.visualMode) { + operatorArgs.lastSel = { + anchor: copyCursor(sel.anchor), + head: copyCursor(sel.head), + visualBlock: vim2.visualBlock, + visualLine: vim2.visualLine + }; + } + var curStart, curEnd, linewise, mode; + var cmSel; + if (vim2.visualMode) { + curStart = cursorMin(sel.head, sel.anchor); + curEnd = cursorMax(sel.head, sel.anchor); + linewise = vim2.visualLine || operatorArgs.linewise; + mode = vim2.visualBlock ? "block" : linewise ? "line" : "char"; + var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd); + cmSel = makeCmSelection(cm, { + anchor: newPositions.start, + head: newPositions.end + }, mode); + if (linewise) { + var ranges = cmSel.ranges; + if (mode == "block") { + for (var i = 0; i < ranges.length; i++) { + ranges[i].head.ch = lineLength(cm, ranges[i].head.line); + } + } else if (mode == "line") { + ranges[0].head = new Pos2(ranges[0].head.line + 1, 0); + } + } + } else { + curStart = copyCursor(newAnchor || oldAnchor); + curEnd = copyCursor(newHead || oldHead); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curStart; + curStart = curEnd; + curEnd = tmp; + } + linewise = motionArgs.linewise || operatorArgs.linewise; + if (linewise) { + expandSelectionToLine(cm, curStart, curEnd); + } else if (motionArgs.forward) { + clipToLine(cm, curStart, curEnd); + } + mode = "char"; + var exclusive = !motionArgs.inclusive || linewise; + var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd); + cmSel = makeCmSelection(cm, { + anchor: newPositions.start, + head: newPositions.end + }, mode, exclusive); + } + cm.setSelections(cmSel.ranges, cmSel.primary); + vim2.lastMotion = null; + operatorArgs.repeat = repeat; + operatorArgs.registerName = registerName; + operatorArgs.linewise = linewise; + var operatorMoveTo = operators[operator]( + cm, + operatorArgs, + cmSel.ranges, + oldAnchor, + newHead + ); + if (vim2.visualMode) { + exitVisualMode(cm, operatorMoveTo != null); + } + if (operatorMoveTo) { + cm.setCursor(operatorMoveTo); + } + } + }, + recordLastEdit: function(vim2, inputState, actionCommand) { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { + return; + } + vim2.lastEditInputState = inputState; + vim2.lastEditActionCommand = actionCommand; + macroModeState.lastInsertModeChanges.changes = []; + macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false; + macroModeState.lastInsertModeChanges.visualBlock = vim2.visualBlock ? vim2.sel.head.line - vim2.sel.anchor.line : 0; + } + }; + var motions = { + moveToTopLine: function(cm, _head, motionArgs) { + var line = getUserVisibleLines(cm).top + motionArgs.repeat - 1; + return new Pos2(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + moveToMiddleLine: function(cm) { + var range = getUserVisibleLines(cm); + var line = Math.floor((range.top + range.bottom) * 0.5); + return new Pos2(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + moveToBottomLine: function(cm, _head, motionArgs) { + var line = getUserVisibleLines(cm).bottom - motionArgs.repeat + 1; + return new Pos2(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + }, + expandToLine: function(_cm, head, motionArgs) { + var cur2 = head; + return new Pos2(cur2.line + motionArgs.repeat - 1, Infinity); + }, + findNext: function(cm, _head, motionArgs) { + var state = getSearchState(cm); + var query = state.getQuery(); + if (!query) { + return; + } + var prev = !motionArgs.forward; + prev = state.isReversed() ? !prev : prev; + highlightSearchMatches(cm, query); + return findNext(cm, prev, query, motionArgs.repeat); + }, + /** + * Find and select the next occurrence of the search query. If the cursor is currently + * within a match, then find and select the current match. Otherwise, find the next occurrence in the + * appropriate direction. + * + * This differs from `findNext` in the following ways: + * + * 1. Instead of only returning the "from", this returns a "from", "to" range. + * 2. If the cursor is currently inside a search match, this selects the current match + * instead of the next match. + * 3. If there is no associated operator, this will turn on visual mode. + */ + findAndSelectNextInclusive: function(cm, _head, motionArgs, vim2, prevInputState) { + var state = getSearchState(cm); + var query = state.getQuery(); + if (!query) { + return; + } + var prev = !motionArgs.forward; + prev = state.isReversed() ? !prev : prev; + var next = findNextFromAndToInclusive(cm, prev, query, motionArgs.repeat, vim2); + if (!next) { + return; + } + if (prevInputState.operator) { + return next; + } + var from = next[0]; + var to = new Pos2(next[1].line, next[1].ch - 1); + if (vim2.visualMode) { + if (vim2.visualLine || vim2.visualBlock) { + vim2.visualLine = false; + vim2.visualBlock = false; + CodeMirror2.signal(cm, "vim-mode-change", { mode: "visual", subMode: "" }); + } + var anchor = vim2.sel.anchor; + if (anchor) { + if (state.isReversed()) { + if (motionArgs.forward) { + return [anchor, from]; + } + return [anchor, to]; + } else { + if (motionArgs.forward) { + return [anchor, to]; + } + return [anchor, from]; + } + } + } else { + vim2.visualMode = true; + vim2.visualLine = false; + vim2.visualBlock = false; + CodeMirror2.signal(cm, "vim-mode-change", { mode: "visual", subMode: "" }); + } + return prev ? [to, from] : [from, to]; + }, + goToMark: function(cm, _head, motionArgs, vim2) { + var pos = getMarkPos(cm, vim2, motionArgs.selectedCharacter); + if (pos) { + return motionArgs.linewise ? { line: pos.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(pos.line)) } : pos; + } + return null; + }, + moveToOtherHighlightedEnd: function(cm, _head, motionArgs, vim2) { + if (vim2.visualBlock && motionArgs.sameLine) { + var sel = vim2.sel; + return [ + clipCursorToContent(cm, new Pos2(sel.anchor.line, sel.head.ch)), + clipCursorToContent(cm, new Pos2(sel.head.line, sel.anchor.ch)) + ]; + } else { + return [vim2.sel.head, vim2.sel.anchor]; + } + }, + jumpToMark: function(cm, head, motionArgs, vim2) { + var best = head; + for (var i = 0; i < motionArgs.repeat; i++) { + var cursor = best; + for (var key in vim2.marks) { + if (!isLowerCase(key)) { + continue; + } + var mark = vim2.marks[key].find(); + var isWrongDirection = motionArgs.forward ? cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark); + if (isWrongDirection) { + continue; + } + if (motionArgs.linewise && mark.line == cursor.line) { + continue; + } + var equal = cursorEqual(cursor, best); + var between = motionArgs.forward ? cursorIsBetween(cursor, mark, best) : cursorIsBetween(best, mark, cursor); + if (equal || between) { + best = mark; + } + } + } + if (motionArgs.linewise) { + best = new Pos2(best.line, findFirstNonWhiteSpaceCharacter(cm.getLine(best.line))); + } + return best; + }, + moveByCharacters: function(_cm, head, motionArgs) { + var cur2 = head; + var repeat = motionArgs.repeat; + var ch = motionArgs.forward ? cur2.ch + repeat : cur2.ch - repeat; + return new Pos2(cur2.line, ch); + }, + moveByLines: function(cm, head, motionArgs, vim2) { + var cur2 = head; + var endCh = cur2.ch; + switch (vim2.lastMotion) { + case this.moveByLines: + case this.moveByDisplayLines: + case this.moveByScroll: + case this.moveToColumn: + case this.moveToEol: + endCh = vim2.lastHPos; + break; + default: + vim2.lastHPos = endCh; + } + var repeat = motionArgs.repeat + (motionArgs.repeatOffset || 0); + var line = motionArgs.forward ? cur2.line + repeat : cur2.line - repeat; + var first = cm.firstLine(); + var last = cm.lastLine(); + var posV = cm.findPosV(cur2, motionArgs.forward ? repeat : -repeat, "line", vim2.lastHSPos); + var hasMarkedText = motionArgs.forward ? posV.line > line : posV.line < line; + if (hasMarkedText) { + line = posV.line; + endCh = posV.ch; + } + if (line < first && cur2.line == first) { + return this.moveToStartOfLine(cm, head, motionArgs, vim2); + } else if (line > last && cur2.line == last) { + return moveToEol(cm, head, motionArgs, vim2, true); + } + if (motionArgs.toFirstChar) { + endCh = findFirstNonWhiteSpaceCharacter(cm.getLine(line)); + vim2.lastHPos = endCh; + } + vim2.lastHSPos = cm.charCoords(new Pos2(line, endCh), "div").left; + return new Pos2(line, endCh); + }, + moveByDisplayLines: function(cm, head, motionArgs, vim2) { + var cur2 = head; + switch (vim2.lastMotion) { + case this.moveByDisplayLines: + case this.moveByScroll: + case this.moveByLines: + case this.moveToColumn: + case this.moveToEol: + break; + default: + vim2.lastHSPos = cm.charCoords(cur2, "div").left; + } + var repeat = motionArgs.repeat; + var res = cm.findPosV(cur2, motionArgs.forward ? repeat : -repeat, "line", vim2.lastHSPos); + if (res.hitSide) { + if (motionArgs.forward) { + var lastCharCoords = cm.charCoords(res, "div"); + var goalCoords = { top: lastCharCoords.top + 8, left: vim2.lastHSPos }; + var res = cm.coordsChar(goalCoords, "div"); + } else { + var resCoords = cm.charCoords(new Pos2(cm.firstLine(), 0), "div"); + resCoords.left = vim2.lastHSPos; + res = cm.coordsChar(resCoords, "div"); + } + } + vim2.lastHPos = res.ch; + return res; + }, + moveByPage: function(cm, head, motionArgs) { + var curStart = head; + var repeat = motionArgs.repeat; + return cm.findPosV(curStart, motionArgs.forward ? repeat : -repeat, "page"); + }, + moveByParagraph: function(cm, head, motionArgs) { + var dir = motionArgs.forward ? 1 : -1; + return findParagraph(cm, head, motionArgs.repeat, dir); + }, + moveBySentence: function(cm, head, motionArgs) { + var dir = motionArgs.forward ? 1 : -1; + return findSentence(cm, head, motionArgs.repeat, dir); + }, + moveByScroll: function(cm, head, motionArgs, vim2) { + var scrollbox = cm.getScrollInfo(); + var curEnd = null; + var repeat = motionArgs.repeat; + if (!repeat) { + repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight()); + } + var orig = cm.charCoords(head, "local"); + motionArgs.repeat = repeat; + curEnd = motions.moveByDisplayLines(cm, head, motionArgs, vim2); + if (!curEnd) { + return null; + } + var dest = cm.charCoords(curEnd, "local"); + cm.scrollTo(null, scrollbox.top + dest.top - orig.top); + return curEnd; + }, + moveByWords: function(cm, head, motionArgs) { + return moveToWord( + cm, + head, + motionArgs.repeat, + !!motionArgs.forward, + !!motionArgs.wordEnd, + !!motionArgs.bigWord + ); + }, + moveTillCharacter: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + var curEnd = moveToCharacter( + cm, + repeat, + motionArgs.forward, + motionArgs.selectedCharacter, + head + ); + var increment = motionArgs.forward ? -1 : 1; + recordLastCharacterSearch(increment, motionArgs); + if (!curEnd) + return null; + curEnd.ch += increment; + return curEnd; + }, + moveToCharacter: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + recordLastCharacterSearch(0, motionArgs); + return moveToCharacter( + cm, + repeat, + motionArgs.forward, + motionArgs.selectedCharacter, + head + ) || head; + }, + moveToSymbol: function(cm, head, motionArgs) { + var repeat = motionArgs.repeat; + return findSymbol( + cm, + repeat, + motionArgs.forward, + motionArgs.selectedCharacter + ) || head; + }, + moveToColumn: function(cm, head, motionArgs, vim2) { + var repeat = motionArgs.repeat; + vim2.lastHPos = repeat - 1; + vim2.lastHSPos = cm.charCoords(head, "div").left; + return moveToColumn(cm, repeat); + }, + moveToEol: function(cm, head, motionArgs, vim2) { + return moveToEol(cm, head, motionArgs, vim2, false); + }, + moveToFirstNonWhiteSpaceCharacter: function(cm, head) { + var cursor = head; + return new Pos2( + cursor.line, + findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)) + ); + }, + moveToMatchedSymbol: function(cm, head) { + var cursor = head; + var line = cursor.line; + var ch = cursor.ch; + var lineText = cm.getLine(line); + var symbol; + for (; ch < lineText.length; ch++) { + symbol = lineText.charAt(ch); + if (symbol && isMatchableSymbol(symbol)) { + var style = cm.getTokenTypeAt(new Pos2(line, ch + 1)); + if (style !== "string" && style !== "comment") { + break; + } + } + } + if (ch < lineText.length) { + var re = ch === "<" || ch === ">" ? /[(){}[\]<>]/ : /[(){}[\]]/; + var matched = cm.findMatchingBracket(new Pos2(line, ch), { bracketRegex: re }); + return matched.to; + } else { + return cursor; + } + }, + moveToStartOfLine: function(_cm, head) { + return new Pos2(head.line, 0); + }, + moveToLineOrEdgeOfDocument: function(cm, _head, motionArgs) { + var lineNum = motionArgs.forward ? cm.lastLine() : cm.firstLine(); + if (motionArgs.repeatIsExplicit) { + lineNum = motionArgs.repeat - cm.getOption("firstLineNumber"); + } + return new Pos2( + lineNum, + findFirstNonWhiteSpaceCharacter(cm.getLine(lineNum)) + ); + }, + moveToStartOfDisplayLine: function(cm) { + cm.execCommand("goLineLeft"); + return cm.getCursor(); + }, + moveToEndOfDisplayLine: function(cm) { + cm.execCommand("goLineRight"); + var head = cm.getCursor(); + if (head.sticky == "before") + head.ch--; + return head; + }, + textObjectManipulation: function(cm, head, motionArgs, vim2) { + var mirroredPairs = { + "(": ")", + ")": "(", + "{": "}", + "}": "{", + "[": "]", + "]": "[", + "<": ">", + ">": "<" + }; + var selfPaired = { "'": true, '"': true, "`": true }; + var character = motionArgs.selectedCharacter; + if (character == "b") { + character = "("; + } else if (character == "B") { + character = "{"; + } + var inclusive = !motionArgs.textObjectInner; + var tmp, move; + if (mirroredPairs[character]) { + move = true; + tmp = selectCompanionObject(cm, head, character, inclusive); + if (!tmp) { + var sc = cm.getSearchCursor(new RegExp("\\" + character, "g"), head); + if (sc.find()) { + tmp = selectCompanionObject(cm, sc.from(), character, inclusive); + } + } + } else if (selfPaired[character]) { + move = true; + tmp = findBeginningAndEnd(cm, head, character, inclusive); + } else if (character === "W") { + tmp = expandWordUnderCursor( + cm, + inclusive, + !inclusive, + true + /** bigWord */ + ); + } else if (character === "w") { + tmp = expandWordUnderCursor( + cm, + inclusive, + !inclusive, + false + /** bigWord */ + ); + } else if (character === "p") { + tmp = findParagraph(cm, head, motionArgs.repeat, 0, inclusive); + motionArgs.linewise = true; + if (vim2.visualMode) { + if (!vim2.visualLine) { + vim2.visualLine = true; + } + } else { + var operatorArgs = vim2.inputState.operatorArgs; + if (operatorArgs) { + operatorArgs.linewise = true; + } + tmp.end.line--; + } + } else if (character === "t") { + tmp = expandTagUnderCursor(cm, head, inclusive); + } else if (character === "s") { + var content = cm.getLine(head.line); + if (head.ch > 0 && isEndOfSentenceSymbol(content[head.ch])) { + head.ch -= 1; + } + var end = getSentence(cm, head, motionArgs.repeat, 1, inclusive); + var start = getSentence(cm, head, motionArgs.repeat, -1, inclusive); + if (isWhiteSpaceString(cm.getLine(start.line)[start.ch]) && isWhiteSpaceString(cm.getLine(end.line)[end.ch - 1])) { + start = { line: start.line, ch: start.ch + 1 }; + } + tmp = { start, end }; + } + if (!tmp) { + return null; + } + if (!cm.state.vim.visualMode) { + return [tmp.start, tmp.end]; + } else { + return expandSelection(cm, tmp.start, tmp.end, move); + } + }, + repeatLastCharacterSearch: function(cm, head, motionArgs) { + var lastSearch = vimGlobalState.lastCharacterSearch; + var repeat = motionArgs.repeat; + var forward = motionArgs.forward === lastSearch.forward; + var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1); + cm.moveH(-increment, "char"); + motionArgs.inclusive = forward ? true : false; + var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter); + if (!curEnd) { + cm.moveH(increment, "char"); + return head; + } + curEnd.ch += increment; + return curEnd; + } + }; + function defineMotion(name, fn) { + motions[name] = fn; + } + function fillArray(val, times) { + var arr = []; + for (var i = 0; i < times; i++) { + arr.push(val); + } + return arr; + } + var operators = { + change: function(cm, args, ranges) { + var finalHead, text; + var vim2 = cm.state.vim; + var anchor = ranges[0].anchor, head = ranges[0].head; + if (!vim2.visualMode) { + text = cm.getRange(anchor, head); + var lastState = vim2.lastEditInputState || {}; + if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) { + var match = /\s+$/.exec(text); + if (match && lastState.motionArgs && lastState.motionArgs.forward) { + head = offsetCursor(head, 0, -match[0].length); + text = text.slice(0, -match[0].length); + } + } + var prevLineEnd = new Pos2(anchor.line - 1, Number.MAX_VALUE); + var wasLastLine = cm.firstLine() == cm.lastLine(); + if (head.line > cm.lastLine() && args.linewise && !wasLastLine) { + cm.replaceRange("", prevLineEnd, head); + } else { + cm.replaceRange("", anchor, head); + } + if (args.linewise) { + if (!wasLastLine) { + cm.setCursor(prevLineEnd); + CodeMirror2.commands.newlineAndIndent(cm); + } + anchor.ch = Number.MAX_VALUE; + } + finalHead = anchor; + } else if (args.fullLine) { + head.ch = Number.MAX_VALUE; + head.line--; + cm.setSelection(anchor, head); + text = cm.getSelection(); + cm.replaceSelection(""); + finalHead = anchor; + } else { + text = cm.getSelection(); + var replacement = fillArray("", ranges.length); + cm.replaceSelections(replacement); + finalHead = cursorMin(ranges[0].head, ranges[0].anchor); + } + vimGlobalState.registerController.pushText( + args.registerName, + "change", + text, + args.linewise, + ranges.length > 1 + ); + actions.enterInsertMode(cm, { head: finalHead }, cm.state.vim); + }, + // delete is a javascript keyword. + "delete": function(cm, args, ranges) { + var finalHead, text; + var vim2 = cm.state.vim; + if (!vim2.visualBlock) { + var anchor = ranges[0].anchor, head = ranges[0].head; + if (args.linewise && head.line != cm.firstLine() && anchor.line == cm.lastLine() && anchor.line == head.line - 1) { + if (anchor.line == cm.firstLine()) { + anchor.ch = 0; + } else { + anchor = new Pos2(anchor.line - 1, lineLength(cm, anchor.line - 1)); + } + } + text = cm.getRange(anchor, head); + cm.replaceRange("", anchor, head); + finalHead = anchor; + if (args.linewise) { + finalHead = motions.moveToFirstNonWhiteSpaceCharacter(cm, anchor); + } + } else { + text = cm.getSelection(); + var replacement = fillArray("", ranges.length); + cm.replaceSelections(replacement); + finalHead = cursorMin(ranges[0].head, ranges[0].anchor); + } + vimGlobalState.registerController.pushText( + args.registerName, + "delete", + text, + args.linewise, + vim2.visualBlock + ); + return clipCursorToContent(cm, finalHead); + }, + indent: function(cm, args, ranges) { + var vim2 = cm.state.vim; + if (cm.indentMore) { + var repeat = vim2.visualMode ? args.repeat : 1; + for (var j = 0; j < repeat; j++) { + if (args.indentRight) + cm.indentMore(); + else + cm.indentLess(); + } + } else { + var startLine = ranges[0].anchor.line; + var endLine = vim2.visualBlock ? ranges[ranges.length - 1].anchor.line : ranges[0].head.line; + var repeat = vim2.visualMode ? args.repeat : 1; + if (args.linewise) { + endLine--; + } + for (var i = startLine; i <= endLine; i++) { + for (var j = 0; j < repeat; j++) { + cm.indentLine(i, args.indentRight); + } + } + } + return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor); + }, + indentAuto: function(cm, _args, ranges) { + cm.execCommand("indentAuto"); + return motions.moveToFirstNonWhiteSpaceCharacter(cm, ranges[0].anchor); + }, + changeCase: function(cm, args, ranges, oldAnchor, newHead) { + var selections = cm.getSelections(); + var swapped = []; + var toLower = args.toLower; + for (var j = 0; j < selections.length; j++) { + var toSwap = selections[j]; + var text = ""; + if (toLower === true) { + text = toSwap.toLowerCase(); + } else if (toLower === false) { + text = toSwap.toUpperCase(); + } else { + for (var i = 0; i < toSwap.length; i++) { + var character = toSwap.charAt(i); + text += isUpperCase(character) ? character.toLowerCase() : character.toUpperCase(); + } + } + swapped.push(text); + } + cm.replaceSelections(swapped); + if (args.shouldMoveCursor) { + return newHead; + } else if (!cm.state.vim.visualMode && args.linewise && ranges[0].anchor.line + 1 == ranges[0].head.line) { + return motions.moveToFirstNonWhiteSpaceCharacter(cm, oldAnchor); + } else if (args.linewise) { + return oldAnchor; + } else { + return cursorMin(ranges[0].anchor, ranges[0].head); + } + }, + yank: function(cm, args, ranges, oldAnchor) { + var vim2 = cm.state.vim; + var text = cm.getSelection(); + var endPos = vim2.visualMode ? cursorMin(vim2.sel.anchor, vim2.sel.head, ranges[0].head, ranges[0].anchor) : oldAnchor; + vimGlobalState.registerController.pushText( + args.registerName, + "yank", + text, + args.linewise, + vim2.visualBlock + ); + return endPos; + } + }; + function defineOperator(name, fn) { + operators[name] = fn; + } + var actions = { + jumpListWalk: function(cm, actionArgs, vim2) { + if (vim2.visualMode) { + return; + } + var repeat = actionArgs.repeat; + var forward = actionArgs.forward; + var jumpList = vimGlobalState.jumpList; + var mark = jumpList.move(cm, forward ? repeat : -repeat); + var markPos = mark ? mark.find() : void 0; + markPos = markPos ? markPos : cm.getCursor(); + cm.setCursor(markPos); + }, + scroll: function(cm, actionArgs, vim2) { + if (vim2.visualMode) { + return; + } + var repeat = actionArgs.repeat || 1; + var lineHeight = cm.defaultTextHeight(); + var top = cm.getScrollInfo().top; + var delta = lineHeight * repeat; + var newPos = actionArgs.forward ? top + delta : top - delta; + var cursor = copyCursor(cm.getCursor()); + var cursorCoords = cm.charCoords(cursor, "local"); + if (actionArgs.forward) { + if (newPos > cursorCoords.top) { + cursor.line += (newPos - cursorCoords.top) / lineHeight; + cursor.line = Math.ceil(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, "local"); + cm.scrollTo(null, cursorCoords.top); + } else { + cm.scrollTo(null, newPos); + } + } else { + var newBottom = newPos + cm.getScrollInfo().clientHeight; + if (newBottom < cursorCoords.bottom) { + cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight; + cursor.line = Math.floor(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, "local"); + cm.scrollTo( + null, + cursorCoords.bottom - cm.getScrollInfo().clientHeight + ); + } else { + cm.scrollTo(null, newPos); + } + } + }, + scrollToCursor: function(cm, actionArgs) { + var lineNum = cm.getCursor().line; + var charCoords = cm.charCoords(new Pos2(lineNum, 0), "local"); + var height = cm.getScrollInfo().clientHeight; + var y = charCoords.top; + switch (actionArgs.position) { + case "center": + y = charCoords.bottom - height / 2; + break; + case "bottom": + var lineLastCharPos = new Pos2(lineNum, cm.getLine(lineNum).length - 1); + var lineLastCharCoords = cm.charCoords(lineLastCharPos, "local"); + var lineHeight = lineLastCharCoords.bottom - y; + y = y - height + lineHeight; + break; + } + cm.scrollTo(null, y); + }, + replayMacro: function(cm, actionArgs, vim2) { + var registerName = actionArgs.selectedCharacter; + var repeat = actionArgs.repeat; + var macroModeState = vimGlobalState.macroModeState; + if (registerName == "@") { + registerName = macroModeState.latestRegister; + } else { + macroModeState.latestRegister = registerName; + } + while (repeat--) { + executeMacroRegister(cm, vim2, macroModeState, registerName); + } + }, + enterMacroRecordMode: function(cm, actionArgs) { + var macroModeState = vimGlobalState.macroModeState; + var registerName = actionArgs.selectedCharacter; + if (vimGlobalState.registerController.isValidRegister(registerName)) { + macroModeState.enterMacroRecordMode(cm, registerName); + } + }, + toggleOverwrite: function(cm) { + if (!cm.state.overwrite) { + cm.toggleOverwrite(true); + cm.setOption("keyMap", "vim-replace"); + CodeMirror2.signal(cm, "vim-mode-change", { mode: "replace" }); + } else { + cm.toggleOverwrite(false); + cm.setOption("keyMap", "vim-insert"); + CodeMirror2.signal(cm, "vim-mode-change", { mode: "insert" }); + } + }, + enterInsertMode: function(cm, actionArgs, vim2) { + if (cm.getOption("readOnly")) { + return; + } + vim2.insertMode = true; + vim2.insertModeRepeat = actionArgs && actionArgs.repeat || 1; + var insertAt = actionArgs ? actionArgs.insertAt : null; + var sel = vim2.sel; + var head = actionArgs.head || cm.getCursor("head"); + var height = cm.listSelections().length; + if (insertAt == "eol") { + head = new Pos2(head.line, lineLength(cm, head.line)); + } else if (insertAt == "bol") { + head = new Pos2(head.line, 0); + } else if (insertAt == "charAfter") { + var newPosition = updateSelectionForSurrogateCharacters(cm, head, offsetCursor(head, 0, 1)); + head = newPosition.end; + } else if (insertAt == "firstNonBlank") { + var newPosition = updateSelectionForSurrogateCharacters(cm, head, motions.moveToFirstNonWhiteSpaceCharacter(cm, head)); + head = newPosition.end; + } else if (insertAt == "startOfSelectedArea") { + if (!vim2.visualMode) + return; + if (!vim2.visualBlock) { + if (sel.head.line < sel.anchor.line) { + head = sel.head; + } else { + head = new Pos2(sel.anchor.line, 0); + } + } else { + head = new Pos2( + Math.min(sel.head.line, sel.anchor.line), + Math.min(sel.head.ch, sel.anchor.ch) + ); + height = Math.abs(sel.head.line - sel.anchor.line) + 1; + } + } else if (insertAt == "endOfSelectedArea") { + if (!vim2.visualMode) + return; + if (!vim2.visualBlock) { + if (sel.head.line >= sel.anchor.line) { + head = offsetCursor(sel.head, 0, 1); + } else { + head = new Pos2(sel.anchor.line, 0); + } + } else { + head = new Pos2( + Math.min(sel.head.line, sel.anchor.line), + Math.max(sel.head.ch, sel.anchor.ch) + 1 + ); + height = Math.abs(sel.head.line - sel.anchor.line) + 1; + } + } else if (insertAt == "inplace") { + if (vim2.visualMode) { + return; + } + } else if (insertAt == "lastEdit") { + head = getLastEditPos(cm) || head; + } + cm.setOption("disableInput", false); + if (actionArgs && actionArgs.replace) { + cm.toggleOverwrite(true); + cm.setOption("keyMap", "vim-replace"); + CodeMirror2.signal(cm, "vim-mode-change", { mode: "replace" }); + } else { + cm.toggleOverwrite(false); + cm.setOption("keyMap", "vim-insert"); + CodeMirror2.signal(cm, "vim-mode-change", { mode: "insert" }); + } + if (!vimGlobalState.macroModeState.isPlaying) { + cm.on("change", onChange); + if (vim2.insertEnd) + vim2.insertEnd.clear(); + vim2.insertEnd = cm.setBookmark(head, { insertLeft: true }); + CodeMirror2.on(cm.getInputField(), "keydown", onKeyEventTargetKeyDown); + } + if (vim2.visualMode) { + exitVisualMode(cm); + } + selectForInsert(cm, head, height); + }, + toggleVisualMode: function(cm, actionArgs, vim2) { + var repeat = actionArgs.repeat; + var anchor = cm.getCursor(); + var head; + if (!vim2.visualMode) { + vim2.visualMode = true; + vim2.visualLine = !!actionArgs.linewise; + vim2.visualBlock = !!actionArgs.blockwise; + head = clipCursorToContent( + cm, + new Pos2(anchor.line, anchor.ch + repeat - 1) + ); + var newPosition = updateSelectionForSurrogateCharacters(cm, anchor, head); + vim2.sel = { + anchor: newPosition.start, + head: newPosition.end + }; + CodeMirror2.signal(cm, "vim-mode-change", { mode: "visual", subMode: vim2.visualLine ? "linewise" : vim2.visualBlock ? "blockwise" : "" }); + updateCmSelection(cm); + updateMark(cm, vim2, "<", cursorMin(anchor, head)); + updateMark(cm, vim2, ">", cursorMax(anchor, head)); + } else if (vim2.visualLine ^ actionArgs.linewise || vim2.visualBlock ^ actionArgs.blockwise) { + vim2.visualLine = !!actionArgs.linewise; + vim2.visualBlock = !!actionArgs.blockwise; + CodeMirror2.signal(cm, "vim-mode-change", { mode: "visual", subMode: vim2.visualLine ? "linewise" : vim2.visualBlock ? "blockwise" : "" }); + updateCmSelection(cm); + } else { + exitVisualMode(cm); + } + }, + reselectLastSelection: function(cm, _actionArgs, vim2) { + var lastSelection = vim2.lastSelection; + if (vim2.visualMode) { + updateLastSelection(cm, vim2); + } + if (lastSelection) { + var anchor = lastSelection.anchorMark.find(); + var head = lastSelection.headMark.find(); + if (!anchor || !head) { + return; + } + vim2.sel = { + anchor, + head + }; + vim2.visualMode = true; + vim2.visualLine = lastSelection.visualLine; + vim2.visualBlock = lastSelection.visualBlock; + updateCmSelection(cm); + updateMark(cm, vim2, "<", cursorMin(anchor, head)); + updateMark(cm, vim2, ">", cursorMax(anchor, head)); + CodeMirror2.signal(cm, "vim-mode-change", { + mode: "visual", + subMode: vim2.visualLine ? "linewise" : vim2.visualBlock ? "blockwise" : "" + }); + } + }, + joinLines: function(cm, actionArgs, vim2) { + var curStart, curEnd; + if (vim2.visualMode) { + curStart = cm.getCursor("anchor"); + curEnd = cm.getCursor("head"); + if (cursorIsBefore(curEnd, curStart)) { + var tmp = curEnd; + curEnd = curStart; + curStart = tmp; + } + curEnd.ch = lineLength(cm, curEnd.line) - 1; + } else { + var repeat = Math.max(actionArgs.repeat, 2); + curStart = cm.getCursor(); + curEnd = clipCursorToContent(cm, new Pos2( + curStart.line + repeat - 1, + Infinity + )); + } + var finalCh = 0; + for (var i = curStart.line; i < curEnd.line; i++) { + finalCh = lineLength(cm, curStart.line); + var text = ""; + var nextStartCh = 0; + if (!actionArgs.keepSpaces) { + var nextLine = cm.getLine(curStart.line + 1); + nextStartCh = nextLine.search(/\S/); + if (nextStartCh == -1) { + nextStartCh = nextLine.length; + } else { + text = " "; + } + } + cm.replaceRange( + text, + new Pos2(curStart.line, finalCh), + new Pos2(curStart.line + 1, nextStartCh) + ); + } + var curFinalPos = clipCursorToContent(cm, new Pos2(curStart.line, finalCh)); + if (vim2.visualMode) { + exitVisualMode(cm, false); + } + cm.setCursor(curFinalPos); + }, + newLineAndEnterInsertMode: function(cm, actionArgs, vim2) { + vim2.insertMode = true; + var insertAt = copyCursor(cm.getCursor()); + if (insertAt.line === cm.firstLine() && !actionArgs.after) { + cm.replaceRange("\n", new Pos2(cm.firstLine(), 0)); + cm.setCursor(cm.firstLine(), 0); + } else { + insertAt.line = actionArgs.after ? insertAt.line : insertAt.line - 1; + insertAt.ch = lineLength(cm, insertAt.line); + cm.setCursor(insertAt); + var newlineFn = CodeMirror2.commands.newlineAndIndentContinueComment || CodeMirror2.commands.newlineAndIndent; + newlineFn(cm); + } + this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim2); + }, + paste: function(cm, actionArgs, vim2) { + var register = vimGlobalState.registerController.getRegister( + actionArgs.registerName + ); + if (actionArgs.registerName === "+") { + navigator.clipboard.readText().then((value) => { + this.continuePaste(cm, actionArgs, vim2, value, register); + }); + } else { + var text = register.toString(); + this.continuePaste(cm, actionArgs, vim2, text, register); + } + }, + continuePaste: function(cm, actionArgs, vim2, text, register) { + var cur2 = copyCursor(cm.getCursor()); + if (!text) { + return; + } + if (actionArgs.matchIndent) { + var tabSize = cm.getOption("tabSize"); + var whitespaceLength = function(str) { + var tabs = str.split(" ").length - 1; + var spaces = str.split(" ").length - 1; + return tabs * tabSize + spaces * 1; + }; + var currentLine = cm.getLine(cm.getCursor().line); + var indent = whitespaceLength(currentLine.match(/^\s*/)[0]); + var chompedText = text.replace(/\n$/, ""); + var wasChomped = text !== chompedText; + var firstIndent = whitespaceLength(text.match(/^\s*/)[0]); + var text = chompedText.replace(/^\s*/gm, function(wspace) { + var newIndent = indent + (whitespaceLength(wspace) - firstIndent); + if (newIndent < 0) { + return ""; + } else if (cm.getOption("indentWithTabs")) { + var quotient = Math.floor(newIndent / tabSize); + return Array(quotient + 1).join(" "); + } else { + return Array(newIndent + 1).join(" "); + } + }); + text += wasChomped ? "\n" : ""; + } + if (actionArgs.repeat > 1) { + var text = Array(actionArgs.repeat + 1).join(text); + } + var linewise = register.linewise; + var blockwise = register.blockwise; + if (blockwise) { + text = text.split("\n"); + if (linewise) { + text.pop(); + } + for (var i = 0; i < text.length; i++) { + text[i] = text[i] == "" ? " " : text[i]; + } + cur2.ch += actionArgs.after ? 1 : 0; + cur2.ch = Math.min(lineLength(cm, cur2.line), cur2.ch); + } else if (linewise) { + if (vim2.visualMode) { + text = vim2.visualLine ? text.slice(0, -1) : "\n" + text.slice(0, text.length - 1) + "\n"; + } else if (actionArgs.after) { + text = "\n" + text.slice(0, text.length - 1); + cur2.ch = lineLength(cm, cur2.line); + } else { + cur2.ch = 0; + } + } else { + cur2.ch += actionArgs.after ? 1 : 0; + } + var curPosFinal; + if (vim2.visualMode) { + vim2.lastPastedText = text; + var lastSelectionCurEnd; + var selectedArea = getSelectedAreaRange(cm, vim2); + var selectionStart = selectedArea[0]; + var selectionEnd = selectedArea[1]; + var selectedText = cm.getSelection(); + var selections = cm.listSelections(); + var emptyStrings = new Array(selections.length).join("1").split("1"); + if (vim2.lastSelection) { + lastSelectionCurEnd = vim2.lastSelection.headMark.find(); + } + vimGlobalState.registerController.unnamedRegister.setText(selectedText); + if (blockwise) { + cm.replaceSelections(emptyStrings); + selectionEnd = new Pos2(selectionStart.line + text.length - 1, selectionStart.ch); + cm.setCursor(selectionStart); + selectBlock(cm, selectionEnd); + cm.replaceSelections(text); + curPosFinal = selectionStart; + } else if (vim2.visualBlock) { + cm.replaceSelections(emptyStrings); + cm.setCursor(selectionStart); + cm.replaceRange(text, selectionStart, selectionStart); + curPosFinal = selectionStart; + } else { + cm.replaceRange(text, selectionStart, selectionEnd); + curPosFinal = cm.posFromIndex(cm.indexFromPos(selectionStart) + text.length - 1); + } + if (lastSelectionCurEnd) { + vim2.lastSelection.headMark = cm.setBookmark(lastSelectionCurEnd); + } + if (linewise) { + curPosFinal.ch = 0; + } + } else { + if (blockwise) { + cm.setCursor(cur2); + for (var i = 0; i < text.length; i++) { + var line = cur2.line + i; + if (line > cm.lastLine()) { + cm.replaceRange("\n", new Pos2(line, 0)); + } + var lastCh = lineLength(cm, line); + if (lastCh < cur2.ch) { + extendLineToColumn(cm, line, cur2.ch); + } + } + cm.setCursor(cur2); + selectBlock(cm, new Pos2(cur2.line + text.length - 1, cur2.ch)); + cm.replaceSelections(text); + curPosFinal = cur2; + } else { + cm.replaceRange(text, cur2); + if (linewise) { + var line = actionArgs.after ? cur2.line + 1 : cur2.line; + curPosFinal = new Pos2(line, findFirstNonWhiteSpaceCharacter(cm.getLine(line))); + } else { + curPosFinal = copyCursor(cur2); + if (!/\n/.test(text)) { + curPosFinal.ch += text.length - (actionArgs.after ? 1 : 0); + } + } + } + } + if (vim2.visualMode) { + exitVisualMode(cm, false); + } + cm.setCursor(curPosFinal); + }, + undo: function(cm, actionArgs) { + cm.operation(function() { + repeatFn(cm, CodeMirror2.commands.undo, actionArgs.repeat)(); + cm.setCursor(clipCursorToContent(cm, cm.getCursor("start"))); + }); + }, + redo: function(cm, actionArgs) { + repeatFn(cm, CodeMirror2.commands.redo, actionArgs.repeat)(); + }, + setRegister: function(_cm, actionArgs, vim2) { + vim2.inputState.registerName = actionArgs.selectedCharacter; + }, + insertRegister: function(cm, actionArgs, vim2) { + var registerName = actionArgs.selectedCharacter; + var register = vimGlobalState.registerController.getRegister(registerName); + var text = register && register.toString(); + if (text) { + cm.replaceSelection(text); + } + }, + oneNormalCommand: function(cm, actionArgs, vim2) { + exitInsertMode(cm, true); + vim2.insertModeReturn = true; + CodeMirror2.on(cm, "vim-command-done", function handler() { + if (vim2.visualMode) + return; + if (vim2.insertModeReturn) { + vim2.insertModeReturn = false; + if (!vim2.insertMode) { + actions.enterInsertMode(cm, {}, vim2); + } + } + CodeMirror2.off(cm, "vim-command-done", handler); + }); + }, + setMark: function(cm, actionArgs, vim2) { + var markName = actionArgs.selectedCharacter; + updateMark(cm, vim2, markName, cm.getCursor()); + }, + replace: function(cm, actionArgs, vim2) { + var replaceWith = actionArgs.selectedCharacter; + var curStart = cm.getCursor(); + var replaceTo; + var curEnd; + var selections = cm.listSelections(); + if (vim2.visualMode) { + curStart = cm.getCursor("start"); + curEnd = cm.getCursor("end"); + } else { + var line = cm.getLine(curStart.line); + replaceTo = curStart.ch + actionArgs.repeat; + if (replaceTo > line.length) { + replaceTo = line.length; + } + curEnd = new Pos2(curStart.line, replaceTo); + } + var newPositions = updateSelectionForSurrogateCharacters(cm, curStart, curEnd); + curStart = newPositions.start; + curEnd = newPositions.end; + if (replaceWith == "\n") { + if (!vim2.visualMode) + cm.replaceRange("", curStart, curEnd); + (CodeMirror2.commands.newlineAndIndentContinueComment || CodeMirror2.commands.newlineAndIndent)(cm); + } else { + var replaceWithStr = cm.getRange(curStart, curEnd); + replaceWithStr = replaceWithStr.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, replaceWith); + replaceWithStr = replaceWithStr.replace(/[^\n]/g, replaceWith); + if (vim2.visualBlock) { + var spaces = new Array(cm.getOption("tabSize") + 1).join(" "); + replaceWithStr = cm.getSelection(); + replaceWithStr = replaceWithStr.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, replaceWith); + replaceWithStr = replaceWithStr.replace(/\t/g, spaces).replace(/[^\n]/g, replaceWith).split("\n"); + cm.replaceSelections(replaceWithStr); + } else { + cm.replaceRange(replaceWithStr, curStart, curEnd); + } + if (vim2.visualMode) { + curStart = cursorIsBefore(selections[0].anchor, selections[0].head) ? selections[0].anchor : selections[0].head; + cm.setCursor(curStart); + exitVisualMode(cm, false); + } else { + cm.setCursor(offsetCursor(curEnd, 0, -1)); + } + } + }, + incrementNumberToken: function(cm, actionArgs) { + var cur2 = cm.getCursor(); + var lineStr = cm.getLine(cur2.line); + var re = /(-?)(?:(0x)([\da-f]+)|(0b|0|)(\d+))/gi; + var match; + var start; + var end; + var numberStr; + while ((match = re.exec(lineStr)) !== null) { + start = match.index; + end = start + match[0].length; + if (cur2.ch < end) + break; + } + if (!actionArgs.backtrack && end <= cur2.ch) + return; + if (match) { + var baseStr = match[2] || match[4]; + var digits = match[3] || match[5]; + var increment = actionArgs.increase ? 1 : -1; + var base = { "0b": 2, "0": 8, "": 10, "0x": 16 }[baseStr.toLowerCase()]; + var number = parseInt(match[1] + digits, base) + increment * actionArgs.repeat; + numberStr = number.toString(base); + var zeroPadding = baseStr ? new Array(digits.length - numberStr.length + 1 + match[1].length).join("0") : ""; + if (numberStr.charAt(0) === "-") { + numberStr = "-" + baseStr + zeroPadding + numberStr.substr(1); + } else { + numberStr = baseStr + zeroPadding + numberStr; + } + var from = new Pos2(cur2.line, start); + var to = new Pos2(cur2.line, end); + cm.replaceRange(numberStr, from, to); + } else { + return; + } + cm.setCursor(new Pos2(cur2.line, start + numberStr.length - 1)); + }, + repeatLastEdit: function(cm, actionArgs, vim2) { + var lastEditInputState = vim2.lastEditInputState; + if (!lastEditInputState) { + return; + } + var repeat = actionArgs.repeat; + if (repeat && actionArgs.repeatIsExplicit) { + vim2.lastEditInputState.repeatOverride = repeat; + } else { + repeat = vim2.lastEditInputState.repeatOverride || repeat; + } + repeatLastEdit( + cm, + vim2, + repeat, + false + /** repeatForInsert */ + ); + }, + indent: function(cm, actionArgs) { + cm.indentLine(cm.getCursor().line, actionArgs.indentRight); + }, + exitInsertMode + }; + function defineAction(name, fn) { + actions[name] = fn; + } + function clipCursorToContent(cm, cur2, oldCur) { + var vim2 = cm.state.vim; + var includeLineBreak = vim2.insertMode || vim2.visualMode; + var line = Math.min(Math.max(cm.firstLine(), cur2.line), cm.lastLine()); + var text = cm.getLine(line); + var maxCh = text.length - 1 + Number(!!includeLineBreak); + var ch = Math.min(Math.max(0, cur2.ch), maxCh); + var charCode = text.charCodeAt(ch); + if (56320 <= charCode && charCode <= 57343) { + var direction = 1; + if (oldCur && oldCur.line == line && oldCur.ch > ch) { + direction = -1; + } + ch += direction; + if (ch > maxCh) + ch -= 2; + } + return new Pos2(line, ch); + } + function copyArgs(args) { + var ret = {}; + for (var prop in args) { + if (args.hasOwnProperty(prop)) { + ret[prop] = args[prop]; + } + } + return ret; + } + function offsetCursor(cur2, offsetLine, offsetCh) { + if (typeof offsetLine === "object") { + offsetCh = offsetLine.ch; + offsetLine = offsetLine.line; + } + return new Pos2(cur2.line + offsetLine, cur2.ch + offsetCh); + } + function commandMatches(keys2, keyMap, context, inputState) { + var operatorPending = inputState.operator; + var match, partial = [], full = []; + var startIndex = noremap ? keyMap.length - defaultKeymapLength : 0; + for (var i = startIndex; i < keyMap.length; i++) { + var command = keyMap[i]; + if (context == "insert" && command.context != "insert" || (command.context == "operatorPending" ? !operatorPending : command.context && command.context != context) || inputState.operator && command.type == "action" || !(match = commandMatch(keys2, command.keys))) { + continue; + } + if (match == "partial") { + partial.push(command); + } + if (match == "full") { + full.push(command); + } + } + return { + partial: partial.length && partial, + full: full.length && full + }; + } + function commandMatch(pressed, mapped) { + if (mapped.slice(-11) == "") { + var prefixLen = mapped.length - 11; + var pressedPrefix = pressed.slice(0, prefixLen); + var mappedPrefix = mapped.slice(0, prefixLen); + return pressedPrefix == mappedPrefix && pressed.length > prefixLen ? "full" : mappedPrefix.indexOf(pressedPrefix) == 0 ? "partial" : false; + } else { + return pressed == mapped ? "full" : mapped.indexOf(pressed) == 0 ? "partial" : false; + } + } + function lastChar(keys2) { + var match = /^.*(<[^>]+>)$/.exec(keys2); + var selectedCharacter = match ? match[1] : keys2.slice(-1); + if (selectedCharacter.length > 1) { + switch (selectedCharacter) { + case "": + selectedCharacter = "\n"; + break; + case "": + selectedCharacter = " "; + break; + default: + selectedCharacter = ""; + break; + } + } + return selectedCharacter; + } + function repeatFn(cm, fn, repeat) { + return function() { + for (var i = 0; i < repeat; i++) { + fn(cm); + } + }; + } + function copyCursor(cur2) { + return new Pos2(cur2.line, cur2.ch); + } + function cursorEqual(cur1, cur2) { + return cur1.ch == cur2.ch && cur1.line == cur2.line; + } + function cursorIsBefore(cur1, cur2) { + if (cur1.line < cur2.line) { + return true; + } + if (cur1.line == cur2.line && cur1.ch < cur2.ch) { + return true; + } + return false; + } + function cursorMin(cur1, cur2) { + if (arguments.length > 2) { + cur2 = cursorMin.apply(void 0, Array.prototype.slice.call(arguments, 1)); + } + return cursorIsBefore(cur1, cur2) ? cur1 : cur2; + } + function cursorMax(cur1, cur2) { + if (arguments.length > 2) { + cur2 = cursorMax.apply(void 0, Array.prototype.slice.call(arguments, 1)); + } + return cursorIsBefore(cur1, cur2) ? cur2 : cur1; + } + function cursorIsBetween(cur1, cur2, cur3) { + var cur1before2 = cursorIsBefore(cur1, cur2); + var cur2before3 = cursorIsBefore(cur2, cur3); + return cur1before2 && cur2before3; + } + function lineLength(cm, lineNum) { + return cm.getLine(lineNum).length; + } + function trim(s) { + if (s.trim) { + return s.trim(); + } + return s.replace(/^\s+|\s+$/g, ""); + } + function escapeRegex(s) { + return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, "\\$1"); + } + function extendLineToColumn(cm, lineNum, column) { + var endCh = lineLength(cm, lineNum); + var spaces = new Array(column - endCh + 1).join(" "); + cm.setCursor(new Pos2(lineNum, endCh)); + cm.replaceRange(spaces, cm.getCursor()); + } + function selectBlock(cm, selectionEnd) { + var selections = [], ranges = cm.listSelections(); + var head = copyCursor(cm.clipPos(selectionEnd)); + var isClipped = !cursorEqual(selectionEnd, head); + var curHead = cm.getCursor("head"); + var primIndex = getIndex(ranges, curHead); + var wasClipped = cursorEqual(ranges[primIndex].head, ranges[primIndex].anchor); + var max = ranges.length - 1; + var index = max - primIndex > primIndex ? max : 0; + var base = ranges[index].anchor; + var firstLine = Math.min(base.line, head.line); + var lastLine = Math.max(base.line, head.line); + var baseCh = base.ch, headCh = head.ch; + var dir = ranges[index].head.ch - baseCh; + var newDir = headCh - baseCh; + if (dir > 0 && newDir <= 0) { + baseCh++; + if (!isClipped) { + headCh--; + } + } else if (dir < 0 && newDir >= 0) { + baseCh--; + if (!wasClipped) { + headCh++; + } + } else if (dir < 0 && newDir == -1) { + baseCh--; + headCh++; + } + for (var line = firstLine; line <= lastLine; line++) { + var range = { anchor: new Pos2(line, baseCh), head: new Pos2(line, headCh) }; + selections.push(range); + } + cm.setSelections(selections); + selectionEnd.ch = headCh; + base.ch = baseCh; + return base; + } + function selectForInsert(cm, head, height) { + var sel = []; + for (var i = 0; i < height; i++) { + var lineHead = offsetCursor(head, i, 0); + sel.push({ anchor: lineHead, head: lineHead }); + } + cm.setSelections(sel, 0); + } + function getIndex(ranges, cursor, end) { + for (var i = 0; i < ranges.length; i++) { + var atAnchor = end != "head" && cursorEqual(ranges[i].anchor, cursor); + var atHead = end != "anchor" && cursorEqual(ranges[i].head, cursor); + if (atAnchor || atHead) { + return i; + } + } + return -1; + } + function getSelectedAreaRange(cm, vim2) { + var lastSelection = vim2.lastSelection; + var getCurrentSelectedAreaRange = function() { + var selections = cm.listSelections(); + var start = selections[0]; + var end = selections[selections.length - 1]; + var selectionStart = cursorIsBefore(start.anchor, start.head) ? start.anchor : start.head; + var selectionEnd = cursorIsBefore(end.anchor, end.head) ? end.head : end.anchor; + return [selectionStart, selectionEnd]; + }; + var getLastSelectedAreaRange = function() { + var selectionStart = cm.getCursor(); + var selectionEnd = cm.getCursor(); + var block = lastSelection.visualBlock; + if (block) { + var width = block.width; + var height = block.height; + selectionEnd = new Pos2(selectionStart.line + height, selectionStart.ch + width); + var selections = []; + for (var i = selectionStart.line; i < selectionEnd.line; i++) { + var anchor = new Pos2(i, selectionStart.ch); + var head = new Pos2(i, selectionEnd.ch); + var range = { anchor, head }; + selections.push(range); + } + cm.setSelections(selections); + } else { + var start = lastSelection.anchorMark.find(); + var end = lastSelection.headMark.find(); + var line = end.line - start.line; + var ch = end.ch - start.ch; + selectionEnd = { line: selectionEnd.line + line, ch: line ? selectionEnd.ch : ch + selectionEnd.ch }; + if (lastSelection.visualLine) { + selectionStart = new Pos2(selectionStart.line, 0); + selectionEnd = new Pos2(selectionEnd.line, lineLength(cm, selectionEnd.line)); + } + cm.setSelection(selectionStart, selectionEnd); + } + return [selectionStart, selectionEnd]; + }; + if (!vim2.visualMode) { + return getLastSelectedAreaRange(); + } else { + return getCurrentSelectedAreaRange(); + } + } + function updateLastSelection(cm, vim2) { + var anchor = vim2.sel.anchor; + var head = vim2.sel.head; + if (vim2.lastPastedText) { + head = cm.posFromIndex(cm.indexFromPos(anchor) + vim2.lastPastedText.length); + vim2.lastPastedText = null; + } + vim2.lastSelection = { + "anchorMark": cm.setBookmark(anchor), + "headMark": cm.setBookmark(head), + "anchor": copyCursor(anchor), + "head": copyCursor(head), + "visualMode": vim2.visualMode, + "visualLine": vim2.visualLine, + "visualBlock": vim2.visualBlock + }; + } + function expandSelection(cm, start, end, move) { + var sel = cm.state.vim.sel; + var head = move ? start : sel.head; + var anchor = move ? start : sel.anchor; + var tmp; + if (cursorIsBefore(end, start)) { + tmp = end; + end = start; + start = tmp; + } + if (cursorIsBefore(head, anchor)) { + head = cursorMin(start, head); + anchor = cursorMax(anchor, end); + } else { + anchor = cursorMin(start, anchor); + head = cursorMax(head, end); + head = offsetCursor(head, 0, -1); + if (head.ch == -1 && head.line != cm.firstLine()) { + head = new Pos2(head.line - 1, lineLength(cm, head.line - 1)); + } + } + return [anchor, head]; + } + function updateCmSelection(cm, sel, mode) { + var vim2 = cm.state.vim; + sel = sel || vim2.sel; + var mode = mode || vim2.visualLine ? "line" : vim2.visualBlock ? "block" : "char"; + var cmSel = makeCmSelection(cm, sel, mode); + cm.setSelections(cmSel.ranges, cmSel.primary); + } + function makeCmSelection(cm, sel, mode, exclusive) { + var head = copyCursor(sel.head); + var anchor = copyCursor(sel.anchor); + if (mode == "char") { + var headOffset = !exclusive && !cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; + var anchorOffset = cursorIsBefore(sel.head, sel.anchor) ? 1 : 0; + head = offsetCursor(sel.head, 0, headOffset); + anchor = offsetCursor(sel.anchor, 0, anchorOffset); + return { + ranges: [{ anchor, head }], + primary: 0 + }; + } else if (mode == "line") { + if (!cursorIsBefore(sel.head, sel.anchor)) { + anchor.ch = 0; + var lastLine = cm.lastLine(); + if (head.line > lastLine) { + head.line = lastLine; + } + head.ch = lineLength(cm, head.line); + } else { + head.ch = 0; + anchor.ch = lineLength(cm, anchor.line); + } + return { + ranges: [{ anchor, head }], + primary: 0 + }; + } else if (mode == "block") { + var top = Math.min(anchor.line, head.line), fromCh = anchor.ch, bottom = Math.max(anchor.line, head.line), toCh = head.ch; + if (fromCh < toCh) { + toCh += 1; + } else { + fromCh += 1; + } + var height = bottom - top + 1; + var primary = head.line == top ? 0 : height - 1; + var ranges = []; + for (var i = 0; i < height; i++) { + ranges.push({ + anchor: new Pos2(top + i, fromCh), + head: new Pos2(top + i, toCh) + }); + } + return { + ranges, + primary + }; + } + } + function getHead(cm) { + var cur2 = cm.getCursor("head"); + if (cm.getSelection().length == 1) { + cur2 = cursorMin(cur2, cm.getCursor("anchor")); + } + return cur2; + } + function exitVisualMode(cm, moveHead) { + var vim2 = cm.state.vim; + if (moveHead !== false) { + cm.setCursor(clipCursorToContent(cm, vim2.sel.head)); + } + updateLastSelection(cm, vim2); + vim2.visualMode = false; + vim2.visualLine = false; + vim2.visualBlock = false; + if (!vim2.insertMode) + CodeMirror2.signal(cm, "vim-mode-change", { mode: "normal" }); + } + function clipToLine(cm, curStart, curEnd) { + var selection = cm.getRange(curStart, curEnd); + if (/\n\s*$/.test(selection)) { + var lines = selection.split("\n"); + lines.pop(); + var line; + for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) { + curEnd.line--; + curEnd.ch = 0; + } + if (line) { + curEnd.line--; + curEnd.ch = lineLength(cm, curEnd.line); + } else { + curEnd.ch = 0; + } + } + } + function expandSelectionToLine(_cm, curStart, curEnd) { + curStart.ch = 0; + curEnd.ch = 0; + curEnd.line++; + } + function findFirstNonWhiteSpaceCharacter(text) { + if (!text) { + return 0; + } + var firstNonWS = text.search(/\S/); + return firstNonWS == -1 ? text.length : firstNonWS; + } + function expandWordUnderCursor(cm, inclusive, innerWord, bigWord, noSymbol) { + var cur2 = getHead(cm); + var line = cm.getLine(cur2.line); + var idx = cur2.ch; + var test = noSymbol ? wordCharTest[0] : bigWordCharTest[0]; + if (innerWord && /\s/.test(line.charAt(idx))) { + test = function(ch) { + return /\s/.test(ch); + }; + } else { + while (!test(line.charAt(idx))) { + idx++; + if (idx >= line.length) { + return null; + } + } + if (bigWord) { + test = bigWordCharTest[0]; + } else { + test = wordCharTest[0]; + if (!test(line.charAt(idx))) { + test = wordCharTest[1]; + } + } + } + var end = idx, start = idx; + while (test(line.charAt(end)) && end < line.length) { + end++; + } + while (test(line.charAt(start)) && start >= 0) { + start--; + } + start++; + if (inclusive) { + var wordEnd = end; + while (/\s/.test(line.charAt(end)) && end < line.length) { + end++; + } + if (wordEnd == end) { + var wordStart = start; + while (/\s/.test(line.charAt(start - 1)) && start > 0) { + start--; + } + if (!start) { + start = wordStart; + } + } + } + return { start: new Pos2(cur2.line, start), end: new Pos2(cur2.line, end) }; + } + function expandTagUnderCursor(cm, head, inclusive) { + var cur2 = head; + if (!CodeMirror2.findMatchingTag || !CodeMirror2.findEnclosingTag) { + return { start: cur2, end: cur2 }; + } + var tags3 = CodeMirror2.findMatchingTag(cm, head) || CodeMirror2.findEnclosingTag(cm, head); + if (!tags3 || !tags3.open || !tags3.close) { + return { start: cur2, end: cur2 }; + } + if (inclusive) { + return { start: tags3.open.from, end: tags3.close.to }; + } + return { start: tags3.open.to, end: tags3.close.from }; + } + function recordJumpPosition(cm, oldCur, newCur) { + if (!cursorEqual(oldCur, newCur)) { + vimGlobalState.jumpList.add(cm, oldCur, newCur); + } + } + function recordLastCharacterSearch(increment, args) { + vimGlobalState.lastCharacterSearch.increment = increment; + vimGlobalState.lastCharacterSearch.forward = args.forward; + vimGlobalState.lastCharacterSearch.selectedCharacter = args.selectedCharacter; + } + var symbolToMode = { + "(": "bracket", + ")": "bracket", + "{": "bracket", + "}": "bracket", + "[": "section", + "]": "section", + "*": "comment", + "/": "comment", + "m": "method", + "M": "method", + "#": "preprocess" + }; + var findSymbolModes = { + bracket: { + isComplete: function(state) { + if (state.nextCh === state.symb) { + state.depth++; + if (state.depth >= 1) + return true; + } else if (state.nextCh === state.reverseSymb) { + state.depth--; + } + return false; + } + }, + section: { + init: function(state) { + state.curMoveThrough = true; + state.symb = (state.forward ? "]" : "[") === state.symb ? "{" : "}"; + }, + isComplete: function(state) { + return state.index === 0 && state.nextCh === state.symb; + } + }, + comment: { + isComplete: function(state) { + var found = state.lastCh === "*" && state.nextCh === "/"; + state.lastCh = state.nextCh; + return found; + } + }, + // TODO: The original Vim implementation only operates on level 1 and 2. + // The current implementation doesn't check for code block level and + // therefore it operates on any levels. + method: { + init: function(state) { + state.symb = state.symb === "m" ? "{" : "}"; + state.reverseSymb = state.symb === "{" ? "}" : "{"; + }, + isComplete: function(state) { + if (state.nextCh === state.symb) + return true; + return false; + } + }, + preprocess: { + init: function(state) { + state.index = 0; + }, + isComplete: function(state) { + if (state.nextCh === "#") { + var token = state.lineText.match(/^#(\w+)/)[1]; + if (token === "endif") { + if (state.forward && state.depth === 0) { + return true; + } + state.depth++; + } else if (token === "if") { + if (!state.forward && state.depth === 0) { + return true; + } + state.depth--; + } + if (token === "else" && state.depth === 0) + return true; + } + return false; + } + } + }; + function findSymbol(cm, repeat, forward, symb) { + var cur2 = copyCursor(cm.getCursor()); + var increment = forward ? 1 : -1; + var endLine = forward ? cm.lineCount() : -1; + var curCh = cur2.ch; + var line = cur2.line; + var lineText = cm.getLine(line); + var state = { + lineText, + nextCh: lineText.charAt(curCh), + lastCh: null, + index: curCh, + symb, + reverseSymb: (forward ? { ")": "(", "}": "{" } : { "(": ")", "{": "}" })[symb], + forward, + depth: 0, + curMoveThrough: false + }; + var mode = symbolToMode[symb]; + if (!mode) + return cur2; + var init = findSymbolModes[mode].init; + var isComplete = findSymbolModes[mode].isComplete; + if (init) { + init(state); + } + while (line !== endLine && repeat) { + state.index += increment; + state.nextCh = state.lineText.charAt(state.index); + if (!state.nextCh) { + line += increment; + state.lineText = cm.getLine(line) || ""; + if (increment > 0) { + state.index = 0; + } else { + var lineLen = state.lineText.length; + state.index = lineLen > 0 ? lineLen - 1 : 0; + } + state.nextCh = state.lineText.charAt(state.index); + } + if (isComplete(state)) { + cur2.line = line; + cur2.ch = state.index; + repeat--; + } + } + if (state.nextCh || state.curMoveThrough) { + return new Pos2(line, state.index); + } + return cur2; + } + function findWord(cm, cur2, forward, bigWord, emptyLineIsWord) { + var lineNum = cur2.line; + var pos = cur2.ch; + var line = cm.getLine(lineNum); + var dir = forward ? 1 : -1; + var charTests = bigWord ? bigWordCharTest : wordCharTest; + if (emptyLineIsWord && line == "") { + lineNum += dir; + line = cm.getLine(lineNum); + if (!isLine(cm, lineNum)) { + return null; + } + pos = forward ? 0 : line.length; + } + while (true) { + if (emptyLineIsWord && line == "") { + return { from: 0, to: 0, line: lineNum }; + } + var stop = dir > 0 ? line.length : -1; + var wordStart = stop, wordEnd = stop; + while (pos != stop) { + var foundWord = false; + for (var i = 0; i < charTests.length && !foundWord; ++i) { + if (charTests[i](line.charAt(pos))) { + wordStart = pos; + while (pos != stop && charTests[i](line.charAt(pos))) { + pos += dir; + } + wordEnd = pos; + foundWord = wordStart != wordEnd; + if (wordStart == cur2.ch && lineNum == cur2.line && wordEnd == wordStart + dir) { + continue; + } else { + return { + from: Math.min(wordStart, wordEnd + 1), + to: Math.max(wordStart, wordEnd), + line: lineNum + }; + } + } + } + if (!foundWord) { + pos += dir; + } + } + lineNum += dir; + if (!isLine(cm, lineNum)) { + return null; + } + line = cm.getLine(lineNum); + pos = dir > 0 ? 0 : line.length; + } + } + function moveToWord(cm, cur2, repeat, forward, wordEnd, bigWord) { + var curStart = copyCursor(cur2); + var words = []; + if (forward && !wordEnd || !forward && wordEnd) { + repeat++; + } + var emptyLineIsWord = !(forward && wordEnd); + for (var i = 0; i < repeat; i++) { + var word = findWord(cm, cur2, forward, bigWord, emptyLineIsWord); + if (!word) { + var eodCh = lineLength(cm, cm.lastLine()); + words.push(forward ? { line: cm.lastLine(), from: eodCh, to: eodCh } : { line: 0, from: 0, to: 0 }); + break; + } + words.push(word); + cur2 = new Pos2(word.line, forward ? word.to - 1 : word.from); + } + var shortCircuit = words.length != repeat; + var firstWord = words[0]; + var lastWord = words.pop(); + if (forward && !wordEnd) { + if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) { + lastWord = words.pop(); + } + return new Pos2(lastWord.line, lastWord.from); + } else if (forward && wordEnd) { + return new Pos2(lastWord.line, lastWord.to - 1); + } else if (!forward && wordEnd) { + if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) { + lastWord = words.pop(); + } + return new Pos2(lastWord.line, lastWord.to); + } else { + return new Pos2(lastWord.line, lastWord.from); + } + } + function moveToEol(cm, head, motionArgs, vim2, keepHPos) { + var cur2 = head; + var retval = new Pos2(cur2.line + motionArgs.repeat - 1, Infinity); + var end = cm.clipPos(retval); + end.ch--; + if (!keepHPos) { + vim2.lastHPos = Infinity; + vim2.lastHSPos = cm.charCoords(end, "div").left; + } + return retval; + } + function moveToCharacter(cm, repeat, forward, character, head) { + var cur2 = head || cm.getCursor(); + var start = cur2.ch; + var idx; + for (var i = 0; i < repeat; i++) { + var line = cm.getLine(cur2.line); + idx = charIdxInLine(start, line, character, forward, true); + if (idx == -1) { + return null; + } + start = idx; + } + return new Pos2(cm.getCursor().line, idx); + } + function moveToColumn(cm, repeat) { + var line = cm.getCursor().line; + return clipCursorToContent(cm, new Pos2(line, repeat - 1)); + } + function updateMark(cm, vim2, markName, pos) { + if (!inArray(markName, validMarks)) { + return; + } + if (vim2.marks[markName]) { + vim2.marks[markName].clear(); + } + vim2.marks[markName] = cm.setBookmark(pos); + } + function charIdxInLine(start, line, character, forward, includeChar) { + var idx; + if (forward) { + idx = line.indexOf(character, start + 1); + if (idx != -1 && !includeChar) { + idx -= 1; + } + } else { + idx = line.lastIndexOf(character, start - 1); + if (idx != -1 && !includeChar) { + idx += 1; + } + } + return idx; + } + function findParagraph(cm, head, repeat, dir, inclusive) { + var line = head.line; + var min = cm.firstLine(); + var max = cm.lastLine(); + var start, end, i = line; + function isEmpty(i2) { + return !cm.getLine(i2); + } + function isBoundary(i2, dir2, any) { + if (any) { + return isEmpty(i2) != isEmpty(i2 + dir2); + } + return !isEmpty(i2) && isEmpty(i2 + dir2); + } + if (dir) { + while (min <= i && i <= max && repeat > 0) { + if (isBoundary(i, dir)) { + repeat--; + } + i += dir; + } + return new Pos2(i, 0); + } + var vim2 = cm.state.vim; + if (vim2.visualLine && isBoundary(line, 1, true)) { + var anchor = vim2.sel.anchor; + if (isBoundary(anchor.line, -1, true)) { + if (!inclusive || anchor.line != line) { + line += 1; + } + } + } + var startState = isEmpty(line); + for (i = line; i <= max && repeat; i++) { + if (isBoundary(i, 1, true)) { + if (!inclusive || isEmpty(i) != startState) { + repeat--; + } + } + } + end = new Pos2(i, 0); + if (i > max && !startState) { + startState = true; + } else { + inclusive = false; + } + for (i = line; i > min; i--) { + if (!inclusive || isEmpty(i) == startState || i == line) { + if (isBoundary(i, -1, true)) { + break; + } + } + } + start = new Pos2(i, 0); + return { start, end }; + } + function getSentence(cm, cur2, repeat, dir, inclusive) { + function nextChar2(curr) { + if (curr.pos + curr.dir < 0 || curr.pos + curr.dir >= curr.line.length) { + curr.line = null; + } else { + curr.pos += curr.dir; + } + } + function forward(cm2, ln, pos, dir2) { + var line = cm2.getLine(ln); + var curr = { + line, + ln, + pos, + dir: dir2 + }; + if (curr.line === "") { + return { ln: curr.ln, pos: curr.pos }; + } + var lastSentencePos = curr.pos; + nextChar2(curr); + while (curr.line !== null) { + lastSentencePos = curr.pos; + if (isEndOfSentenceSymbol(curr.line[curr.pos])) { + if (!inclusive) { + return { ln: curr.ln, pos: curr.pos + 1 }; + } else { + nextChar2(curr); + while (curr.line !== null) { + if (isWhiteSpaceString(curr.line[curr.pos])) { + lastSentencePos = curr.pos; + nextChar2(curr); + } else { + break; + } + } + return { ln: curr.ln, pos: lastSentencePos + 1 }; + } + } + nextChar2(curr); + } + return { ln: curr.ln, pos: lastSentencePos + 1 }; + } + function reverse(cm2, ln, pos, dir2) { + var line = cm2.getLine(ln); + var curr = { + line, + ln, + pos, + dir: dir2 + }; + if (curr.line === "") { + return { ln: curr.ln, pos: curr.pos }; + } + var lastSentencePos = curr.pos; + nextChar2(curr); + while (curr.line !== null) { + if (!isWhiteSpaceString(curr.line[curr.pos]) && !isEndOfSentenceSymbol(curr.line[curr.pos])) { + lastSentencePos = curr.pos; + } else if (isEndOfSentenceSymbol(curr.line[curr.pos])) { + if (!inclusive) { + return { ln: curr.ln, pos: lastSentencePos }; + } else { + if (isWhiteSpaceString(curr.line[curr.pos + 1])) { + return { ln: curr.ln, pos: curr.pos + 1 }; + } else { + return { ln: curr.ln, pos: lastSentencePos }; + } + } + } + nextChar2(curr); + } + curr.line = line; + if (inclusive && isWhiteSpaceString(curr.line[curr.pos])) { + return { ln: curr.ln, pos: curr.pos }; + } else { + return { ln: curr.ln, pos: lastSentencePos }; + } + } + var curr_index = { + ln: cur2.line, + pos: cur2.ch + }; + while (repeat > 0) { + if (dir < 0) { + curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir); + } else { + curr_index = forward(cm, curr_index.ln, curr_index.pos, dir); + } + repeat--; + } + return new Pos2(curr_index.ln, curr_index.pos); + } + function findSentence(cm, cur2, repeat, dir) { + function nextChar2(cm2, idx) { + if (idx.pos + idx.dir < 0 || idx.pos + idx.dir >= idx.line.length) { + idx.ln += idx.dir; + if (!isLine(cm2, idx.ln)) { + idx.line = null; + idx.ln = null; + idx.pos = null; + return; + } + idx.line = cm2.getLine(idx.ln); + idx.pos = idx.dir > 0 ? 0 : idx.line.length - 1; + } else { + idx.pos += idx.dir; + } + } + function forward(cm2, ln, pos, dir2) { + var line = cm2.getLine(ln); + var stop = line === ""; + var curr = { + line, + ln, + pos, + dir: dir2 + }; + var last_valid = { + ln: curr.ln, + pos: curr.pos + }; + var skip_empty_lines = curr.line === ""; + nextChar2(cm2, curr); + while (curr.line !== null) { + last_valid.ln = curr.ln; + last_valid.pos = curr.pos; + if (curr.line === "" && !skip_empty_lines) { + return { ln: curr.ln, pos: curr.pos }; + } else if (stop && curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) { + return { ln: curr.ln, pos: curr.pos }; + } else if (isEndOfSentenceSymbol(curr.line[curr.pos]) && !stop && (curr.pos === curr.line.length - 1 || isWhiteSpaceString(curr.line[curr.pos + 1]))) { + stop = true; + } + nextChar2(cm2, curr); + } + var line = cm2.getLine(last_valid.ln); + last_valid.pos = 0; + for (var i = line.length - 1; i >= 0; --i) { + if (!isWhiteSpaceString(line[i])) { + last_valid.pos = i; + break; + } + } + return last_valid; + } + function reverse(cm2, ln, pos, dir2) { + var line = cm2.getLine(ln); + var curr = { + line, + ln, + pos, + dir: dir2 + }; + var last_valid = { + ln: curr.ln, + pos: null + }; + var skip_empty_lines = curr.line === ""; + nextChar2(cm2, curr); + while (curr.line !== null) { + if (curr.line === "" && !skip_empty_lines) { + if (last_valid.pos !== null) { + return last_valid; + } else { + return { ln: curr.ln, pos: curr.pos }; + } + } else if (isEndOfSentenceSymbol(curr.line[curr.pos]) && last_valid.pos !== null && !(curr.ln === last_valid.ln && curr.pos + 1 === last_valid.pos)) { + return last_valid; + } else if (curr.line !== "" && !isWhiteSpaceString(curr.line[curr.pos])) { + skip_empty_lines = false; + last_valid = { ln: curr.ln, pos: curr.pos }; + } + nextChar2(cm2, curr); + } + var line = cm2.getLine(last_valid.ln); + last_valid.pos = 0; + for (var i = 0; i < line.length; ++i) { + if (!isWhiteSpaceString(line[i])) { + last_valid.pos = i; + break; + } + } + return last_valid; + } + var curr_index = { + ln: cur2.line, + pos: cur2.ch + }; + while (repeat > 0) { + if (dir < 0) { + curr_index = reverse(cm, curr_index.ln, curr_index.pos, dir); + } else { + curr_index = forward(cm, curr_index.ln, curr_index.pos, dir); + } + repeat--; + } + return new Pos2(curr_index.ln, curr_index.pos); + } + function selectCompanionObject(cm, head, symb, inclusive) { + var cur2 = head, start, end; + var bracketRegexp = { + "(": /[()]/, + ")": /[()]/, + "[": /[[\]]/, + "]": /[[\]]/, + "{": /[{}]/, + "}": /[{}]/, + "<": /[<>]/, + ">": /[<>]/ + }[symb]; + var openSym = { + "(": "(", + ")": "(", + "[": "[", + "]": "[", + "{": "{", + "}": "{", + "<": "<", + ">": "<" + }[symb]; + var curChar = cm.getLine(cur2.line).charAt(cur2.ch); + var offset = curChar === openSym ? 1 : 0; + start = cm.scanForBracket(new Pos2(cur2.line, cur2.ch + offset), -1, void 0, { "bracketRegex": bracketRegexp }); + end = cm.scanForBracket(new Pos2(cur2.line, cur2.ch + offset), 1, void 0, { "bracketRegex": bracketRegexp }); + if (!start || !end) + return null; + start = start.pos; + end = end.pos; + if (start.line == end.line && start.ch > end.ch || start.line > end.line) { + var tmp = start; + start = end; + end = tmp; + } + if (inclusive) { + end.ch += 1; + } else { + start.ch += 1; + } + return { start, end }; + } + function findBeginningAndEnd(cm, head, symb, inclusive) { + var cur2 = copyCursor(head); + var line = cm.getLine(cur2.line); + var chars = line.split(""); + var start, end, i, len; + var firstIndex = chars.indexOf(symb); + if (cur2.ch < firstIndex) { + cur2.ch = firstIndex; + } else if (firstIndex < cur2.ch && chars[cur2.ch] == symb) { + var stringAfter = /string/.test(cm.getTokenTypeAt(offsetCursor(head, 0, 1))); + var stringBefore = /string/.test(cm.getTokenTypeAt(head)); + var isStringStart = stringAfter && !stringBefore; + if (!isStringStart) { + end = cur2.ch; + --cur2.ch; + } + } + if (chars[cur2.ch] == symb && !end) { + start = cur2.ch + 1; + } else { + for (i = cur2.ch; i > -1 && !start; i--) { + if (chars[i] == symb) { + start = i + 1; + } + } + } + if (start && !end) { + for (i = start, len = chars.length; i < len && !end; i++) { + if (chars[i] == symb) { + end = i; + } + } + } + if (!start || !end) { + return { start: cur2, end: cur2 }; + } + if (inclusive) { + --start; + ++end; + } + return { + start: new Pos2(cur2.line, start), + end: new Pos2(cur2.line, end) + }; + } + defineOption("pcre", true, "boolean"); + function SearchState() { + } + SearchState.prototype = { + getQuery: function() { + return vimGlobalState.query; + }, + setQuery: function(query) { + vimGlobalState.query = query; + }, + getOverlay: function() { + return this.searchOverlay; + }, + setOverlay: function(overlay) { + this.searchOverlay = overlay; + }, + isReversed: function() { + return vimGlobalState.isReversed; + }, + setReversed: function(reversed) { + vimGlobalState.isReversed = reversed; + }, + getScrollbarAnnotate: function() { + return this.annotate; + }, + setScrollbarAnnotate: function(annotate) { + this.annotate = annotate; + } + }; + function getSearchState(cm) { + var vim2 = cm.state.vim; + return vim2.searchState_ || (vim2.searchState_ = new SearchState()); + } + function splitBySlash(argString) { + return splitBySeparator(argString, "/"); + } + function findUnescapedSlashes(argString) { + return findUnescapedSeparators(argString, "/"); + } + function splitBySeparator(argString, separator) { + var slashes = findUnescapedSeparators(argString, separator) || []; + if (!slashes.length) + return []; + var tokens = []; + if (slashes[0] !== 0) + return; + for (var i = 0; i < slashes.length; i++) { + if (typeof slashes[i] == "number") + tokens.push(argString.substring(slashes[i] + 1, slashes[i + 1])); + } + return tokens; + } + function findUnescapedSeparators(str, separator) { + if (!separator) + separator = "/"; + var escapeNextChar = false; + var slashes = []; + for (var i = 0; i < str.length; i++) { + var c = str.charAt(i); + if (!escapeNextChar && c == separator) { + slashes.push(i); + } + escapeNextChar = !escapeNextChar && c == "\\"; + } + return slashes; + } + function translateRegex(str) { + var specials = "|(){"; + var unescape = "}"; + var escapeNextChar = false; + var out = []; + for (var i = -1; i < str.length; i++) { + var c = str.charAt(i) || ""; + var n = str.charAt(i + 1) || ""; + var specialComesNext = n && specials.indexOf(n) != -1; + if (escapeNextChar) { + if (c !== "\\" || !specialComesNext) { + out.push(c); + } + escapeNextChar = false; + } else { + if (c === "\\") { + escapeNextChar = true; + if (n && unescape.indexOf(n) != -1) { + specialComesNext = true; + } + if (!specialComesNext || n === "\\") { + out.push(c); + } + } else { + out.push(c); + if (specialComesNext && n !== "\\") { + out.push("\\"); + } + } + } + } + return out.join(""); + } + var charUnescapes = { "\\n": "\n", "\\r": "\r", "\\t": " " }; + function translateRegexReplace(str) { + var escapeNextChar = false; + var out = []; + for (var i = -1; i < str.length; i++) { + var c = str.charAt(i) || ""; + var n = str.charAt(i + 1) || ""; + if (charUnescapes[c + n]) { + out.push(charUnescapes[c + n]); + i++; + } else if (escapeNextChar) { + out.push(c); + escapeNextChar = false; + } else { + if (c === "\\") { + escapeNextChar = true; + if (isNumber(n) || n === "$") { + out.push("$"); + } else if (n !== "/" && n !== "\\") { + out.push("\\"); + } + } else { + if (c === "$") { + out.push("$"); + } + out.push(c); + if (n === "/") { + out.push("\\"); + } + } + } + } + return out.join(""); + } + var unescapes = { "\\/": "/", "\\\\": "\\", "\\n": "\n", "\\r": "\r", "\\t": " ", "\\&": "&" }; + function unescapeRegexReplace(str) { + var stream = new CodeMirror2.StringStream(str); + var output = []; + while (!stream.eol()) { + while (stream.peek() && stream.peek() != "\\") { + output.push(stream.next()); + } + var matched = false; + for (var matcher in unescapes) { + if (stream.match(matcher, true)) { + matched = true; + output.push(unescapes[matcher]); + break; + } + } + if (!matched) { + output.push(stream.next()); + } + } + return output.join(""); + } + function parseQuery(query, ignoreCase, smartCase) { + var lastSearchRegister = vimGlobalState.registerController.getRegister("/"); + lastSearchRegister.setText(query); + if (query instanceof RegExp) { + return query; + } + var slashes = findUnescapedSlashes(query); + var regexPart; + var forceIgnoreCase; + if (!slashes.length) { + regexPart = query; + } else { + regexPart = query.substring(0, slashes[0]); + var flagsPart = query.substring(slashes[0]); + forceIgnoreCase = flagsPart.indexOf("i") != -1; + } + if (!regexPart) { + return null; + } + if (!getOption("pcre")) { + regexPart = translateRegex(regexPart); + } + if (smartCase) { + ignoreCase = /^[^A-Z]*$/.test(regexPart); + } + var regexp = new RegExp( + regexPart, + ignoreCase || forceIgnoreCase ? "im" : "m" + ); + return regexp; + } + function dom(n) { + if (typeof n === "string") + n = document.createElement(n); + for (var a, i = 1; i < arguments.length; i++) { + if (!(a = arguments[i])) + continue; + if (typeof a !== "object") + a = document.createTextNode(a); + if (a.nodeType) + n.appendChild(a); + else + for (var key in a) { + if (!Object.prototype.hasOwnProperty.call(a, key)) + continue; + if (key[0] === "$") + n.style[key.slice(1)] = a[key]; + else + n.setAttribute(key, a[key]); + } + } + return n; + } + function showConfirm(cm, template) { + var pre = dom("div", { $color: "red", $whiteSpace: "pre", class: "cm-vim-message" }, template); + if (cm.openNotification) { + cm.openNotification(pre, { bottom: true, duration: 5e3 }); + } else { + alert(pre.innerText); + } + } + function makePrompt(prefix, desc) { + return dom( + "div", + { $display: "flex" }, + dom( + "span", + { $fontFamily: "monospace", $whiteSpace: "pre", $flex: 1 }, + prefix, + dom("input", { + type: "text", + autocorrect: "off", + autocapitalize: "off", + spellcheck: "false", + $width: "100%" + }) + ), + desc && dom("span", { $color: "#888" }, desc) + ); + } + function showPrompt(cm, options2) { + var template = makePrompt(options2.prefix, options2.desc); + if (cm.openDialog) { + cm.openDialog(template, options2.onClose, { + onKeyDown: options2.onKeyDown, + onKeyUp: options2.onKeyUp, + bottom: true, + selectValueOnOpen: false, + value: options2.value + }); + } else { + var shortText = ""; + if (typeof options2.prefix != "string" && options2.prefix) + shortText += options2.prefix.textContent; + if (options2.desc) + shortText += " " + options2.desc; + options2.onClose(prompt(shortText, "")); + } + } + function regexEqual(r1, r2) { + if (r1 instanceof RegExp && r2 instanceof RegExp) { + var props = ["global", "multiline", "ignoreCase", "source"]; + for (var i = 0; i < props.length; i++) { + var prop = props[i]; + if (r1[prop] !== r2[prop]) { + return false; + } + } + return true; + } + return false; + } + function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) { + if (!rawQuery) { + return; + } + var state = getSearchState(cm); + var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase); + if (!query) { + return; + } + highlightSearchMatches(cm, query); + if (regexEqual(query, state.getQuery())) { + return query; + } + state.setQuery(query); + return query; + } + function searchOverlay(query) { + if (query.source.charAt(0) == "^") { + var matchSol = true; + } + return { + token: function(stream) { + if (matchSol && !stream.sol()) { + stream.skipToEnd(); + return; + } + var match = stream.match(query, false); + if (match) { + if (match[0].length == 0) { + stream.next(); + return "searching"; + } + if (!stream.sol()) { + stream.backUp(1); + if (!query.exec(stream.next() + match[0])) { + stream.next(); + return null; + } + } + stream.match(query); + return "searching"; + } + while (!stream.eol()) { + stream.next(); + if (stream.match(query, false)) + break; + } + }, + query + }; + } + var highlightTimeout = 0; + function highlightSearchMatches(cm, query) { + clearTimeout(highlightTimeout); + highlightTimeout = setTimeout(function() { + if (!cm.state.vim) + return; + var searchState = getSearchState(cm); + var overlay = searchState.getOverlay(); + if (!overlay || query != overlay.query) { + if (overlay) { + cm.removeOverlay(overlay); + } + overlay = searchOverlay(query); + cm.addOverlay(overlay); + if (cm.showMatchesOnScrollbar) { + if (searchState.getScrollbarAnnotate()) { + searchState.getScrollbarAnnotate().clear(); + } + searchState.setScrollbarAnnotate(cm.showMatchesOnScrollbar(query)); + } + searchState.setOverlay(overlay); + } + }, 50); + } + function findNext(cm, prev, query, repeat) { + if (repeat === void 0) { + repeat = 1; + } + return cm.operation(function() { + var pos = cm.getCursor(); + var cursor = cm.getSearchCursor(query, pos); + for (var i = 0; i < repeat; i++) { + var found = cursor.find(prev); + if (i == 0 && found && cursorEqual(cursor.from(), pos)) { + var lastEndPos = prev ? cursor.from() : cursor.to(); + found = cursor.find(prev); + if (found && !found[0] && cursorEqual(cursor.from(), lastEndPos)) { + if (cm.getLine(lastEndPos.line).length == lastEndPos.ch) + found = cursor.find(prev); + } + } + if (!found) { + cursor = cm.getSearchCursor( + query, + prev ? new Pos2(cm.lastLine()) : new Pos2(cm.firstLine(), 0) + ); + if (!cursor.find(prev)) { + return; + } + } + } + return cursor.from(); + }); + } + function findNextFromAndToInclusive(cm, prev, query, repeat, vim2) { + if (repeat === void 0) { + repeat = 1; + } + return cm.operation(function() { + var pos = cm.getCursor(); + var cursor = cm.getSearchCursor(query, pos); + var found = cursor.find(!prev); + if (!vim2.visualMode && found && cursorEqual(cursor.from(), pos)) { + cursor.find(!prev); + } + for (var i = 0; i < repeat; i++) { + found = cursor.find(prev); + if (!found) { + cursor = cm.getSearchCursor( + query, + prev ? new Pos2(cm.lastLine()) : new Pos2(cm.firstLine(), 0) + ); + if (!cursor.find(prev)) { + return; + } + } + } + return [cursor.from(), cursor.to()]; + }); + } + function clearSearchHighlight(cm) { + var state = getSearchState(cm); + cm.removeOverlay(getSearchState(cm).getOverlay()); + state.setOverlay(null); + if (state.getScrollbarAnnotate()) { + state.getScrollbarAnnotate().clear(); + state.setScrollbarAnnotate(null); + } + } + function isInRange(pos, start, end) { + if (typeof pos != "number") { + pos = pos.line; + } + if (start instanceof Array) { + return inArray(pos, start); + } else { + if (typeof end == "number") { + return pos >= start && pos <= end; + } else { + return pos == start; + } + } + } + function getUserVisibleLines(cm) { + var scrollInfo = cm.getScrollInfo(); + var occludeToleranceTop = 6; + var occludeToleranceBottom = 10; + var from = cm.coordsChar({ left: 0, top: occludeToleranceTop + scrollInfo.top }, "local"); + var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top; + var to = cm.coordsChar({ left: 0, top: bottomY }, "local"); + return { top: from.line, bottom: to.line }; + } + function getMarkPos(cm, vim2, markName) { + if (markName == "'" || markName == "`") { + return vimGlobalState.jumpList.find(cm, -1) || new Pos2(0, 0); + } else if (markName == ".") { + return getLastEditPos(cm); + } + var mark = vim2.marks[markName]; + return mark && mark.find(); + } + function getLastEditPos(cm) { + if (cm.getLastEditEnd) { + return cm.getLastEditEnd(); + } + var done = cm.doc.history.done; + for (var i = done.length; i--; ) { + if (done[i].changes) { + return copyCursor(done[i].changes[0].to); + } + } + } + var ExCommandDispatcher = function() { + this.buildCommandMap_(); + }; + ExCommandDispatcher.prototype = { + processCommand: function(cm, input, opt_params) { + var that = this; + cm.operation(function() { + cm.curOp.isVimOp = true; + that._processCommand(cm, input, opt_params); + }); + }, + _processCommand: function(cm, input, opt_params) { + var vim2 = cm.state.vim; + var commandHistoryRegister = vimGlobalState.registerController.getRegister(":"); + var previousCommand = commandHistoryRegister.toString(); + var inputStream = new CodeMirror2.StringStream(input); + commandHistoryRegister.setText(input); + var params = opt_params || {}; + params.input = input; + try { + this.parseInput_(cm, inputStream, params); + } catch (e) { + showConfirm(cm, e.toString()); + throw e; + } + if (vim2.visualMode) { + exitVisualMode(cm); + } + var command; + var commandName; + if (!params.commandName) { + if (params.line !== void 0) { + commandName = "move"; + } + } else { + command = this.matchCommand_(params.commandName); + if (command) { + commandName = command.name; + if (command.excludeFromCommandHistory) { + commandHistoryRegister.setText(previousCommand); + } + this.parseCommandArgs_(inputStream, params, command); + if (command.type == "exToKey") { + for (var i = 0; i < command.toKeys.length; i++) { + vimApi.handleKey(cm, command.toKeys[i], "mapping"); + } + return; + } else if (command.type == "exToEx") { + this.processCommand(cm, command.toInput); + return; + } + } + } + if (!commandName) { + showConfirm(cm, 'Not an editor command ":' + input + '"'); + return; + } + try { + exCommands[commandName](cm, params); + if ((!command || !command.possiblyAsync) && params.callback) { + params.callback(); + } + } catch (e) { + showConfirm(cm, e.toString()); + throw e; + } + }, + parseInput_: function(cm, inputStream, result) { + var _a, _b; + inputStream.eatWhile(":"); + if (inputStream.eat("%")) { + result.line = cm.firstLine(); + result.lineEnd = cm.lastLine(); + } else { + result.line = this.parseLineSpec_(cm, inputStream); + if (result.line !== void 0 && inputStream.eat(",")) { + result.lineEnd = this.parseLineSpec_(cm, inputStream); + } + } + if (result.line == void 0) { + if (cm.state.vim.visualMode) { + result.selectionLine = (_a = getMarkPos(cm, cm.state.vim, "<")) == null ? void 0 : _a.line; + result.selectionLineEnd = (_b = getMarkPos(cm, cm.state.vim, ">")) == null ? void 0 : _b.line; + } else { + result.selectionLine = cm.getCursor().line; + } + } else { + result.selectionLine = result.line; + result.selectionLineEnd = result.lineEnd; + } + var commandMatch2 = inputStream.match(/^(\w+|!!|@@|[!#&*<=>@~])/); + if (commandMatch2) { + result.commandName = commandMatch2[1]; + } else { + result.commandName = inputStream.match(/.*/)[0]; + } + return result; + }, + parseLineSpec_: function(cm, inputStream) { + var numberMatch = inputStream.match(/^(\d+)/); + if (numberMatch) { + return parseInt(numberMatch[1], 10) - 1; + } + switch (inputStream.next()) { + case ".": + return this.parseLineSpecOffset_(inputStream, cm.getCursor().line); + case "$": + return this.parseLineSpecOffset_(inputStream, cm.lastLine()); + case "'": + var markName = inputStream.next(); + var markPos = getMarkPos(cm, cm.state.vim, markName); + if (!markPos) + throw new Error("Mark not set"); + return this.parseLineSpecOffset_(inputStream, markPos.line); + case "-": + case "+": + inputStream.backUp(1); + return this.parseLineSpecOffset_(inputStream, cm.getCursor().line); + default: + inputStream.backUp(1); + return void 0; + } + }, + parseLineSpecOffset_: function(inputStream, line) { + var offsetMatch = inputStream.match(/^([+-])?(\d+)/); + if (offsetMatch) { + var offset = parseInt(offsetMatch[2], 10); + if (offsetMatch[1] == "-") { + line -= offset; + } else { + line += offset; + } + } + return line; + }, + parseCommandArgs_: function(inputStream, params, command) { + if (inputStream.eol()) { + return; + } + params.argString = inputStream.match(/.*/)[0]; + var delim = command.argDelimiter || /\s+/; + var args = trim(params.argString).split(delim); + if (args.length && args[0]) { + params.args = args; + } + }, + matchCommand_: function(commandName) { + for (var i = commandName.length; i > 0; i--) { + var prefix = commandName.substring(0, i); + if (this.commandMap_[prefix]) { + var command = this.commandMap_[prefix]; + if (command.name.indexOf(commandName) === 0) { + return command; + } + } + } + return null; + }, + buildCommandMap_: function() { + this.commandMap_ = {}; + for (var i = 0; i < defaultExCommandMap.length; i++) { + var command = defaultExCommandMap[i]; + var key = command.shortName || command.name; + this.commandMap_[key] = command; + } + }, + map: function(lhs, rhs, ctx, noremap2) { + if (lhs != ":" && lhs.charAt(0) == ":") { + if (ctx) { + throw Error("Mode not supported for ex mappings"); + } + var commandName = lhs.substring(1); + if (rhs != ":" && rhs.charAt(0) == ":") { + this.commandMap_[commandName] = { + name: commandName, + type: "exToEx", + toInput: rhs.substring(1), + user: true + }; + } else { + this.commandMap_[commandName] = { + name: commandName, + type: "exToKey", + toKeys: rhs, + user: true + }; + } + } else { + if (rhs != ":" && rhs.charAt(0) == ":") { + var mapping = { + keys: lhs, + type: "keyToEx", + exArgs: { input: rhs.substring(1) } + }; + if (ctx) { + mapping.context = ctx; + } + defaultKeymap2.unshift(mapping); + } else { + var mapping = { + keys: lhs, + type: "keyToKey", + toKeys: rhs, + noremap: noremap2 + }; + if (ctx) { + mapping.context = ctx; + } + defaultKeymap2.unshift(mapping); + } + } + }, + unmap: function(lhs, ctx) { + if (lhs != ":" && lhs.charAt(0) == ":") { + if (ctx) { + throw Error("Mode not supported for ex mappings"); + } + var commandName = lhs.substring(1); + if (this.commandMap_[commandName] && this.commandMap_[commandName].user) { + delete this.commandMap_[commandName]; + return true; + } + } else { + var keys2 = lhs; + for (var i = 0; i < defaultKeymap2.length; i++) { + if (keys2 == defaultKeymap2[i].keys && defaultKeymap2[i].context === ctx) { + defaultKeymap2.splice(i, 1); + return true; + } + } + } + } + }; + var exCommands = { + colorscheme: function(cm, params) { + if (!params.args || params.args.length < 1) { + showConfirm(cm, cm.getOption("theme")); + return; + } + cm.setOption("theme", params.args[0]); + }, + map: function(cm, params, ctx, defaultOnly) { + var mapArgs = params.args; + if (!mapArgs || mapArgs.length < 2) { + if (cm) { + showConfirm(cm, "Invalid mapping: " + params.input); + } + return; + } + exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx, defaultOnly); + }, + imap: function(cm, params) { + this.map(cm, params, "insert"); + }, + nmap: function(cm, params) { + this.map(cm, params, "normal"); + }, + vmap: function(cm, params) { + this.map(cm, params, "visual"); + }, + omap: function(cm, params) { + this.map(cm, params, "operatorPending"); + }, + noremap: function(cm, params) { + this.map(cm, params, void 0, true); + }, + inoremap: function(cm, params) { + this.map(cm, params, "insert", true); + }, + nnoremap: function(cm, params) { + this.map(cm, params, "normal", true); + }, + vnoremap: function(cm, params) { + this.map(cm, params, "visual", true); + }, + onoremap: function(cm, params) { + this.map(cm, params, "operatorPending", true); + }, + unmap: function(cm, params, ctx) { + var mapArgs = params.args; + if (!mapArgs || mapArgs.length < 1 || !exCommandDispatcher.unmap(mapArgs[0], ctx)) { + if (cm) { + showConfirm(cm, "No such mapping: " + params.input); + } + } + }, + mapclear: function(cm, params) { + vimApi.mapclear(); + }, + imapclear: function(cm, params) { + vimApi.mapclear("insert"); + }, + nmapclear: function(cm, params) { + vimApi.mapclear("normal"); + }, + vmapclear: function(cm, params) { + vimApi.mapclear("visual"); + }, + omapclear: function(cm, params) { + vimApi.mapclear("operatorPending"); + }, + move: function(cm, params) { + commandDispatcher.processCommand(cm, cm.state.vim, { + type: "motion", + motion: "moveToLineOrEdgeOfDocument", + motionArgs: { + forward: false, + explicitRepeat: true, + linewise: true + }, + repeatOverride: params.line + 1 + }); + }, + set: function(cm, params) { + var setArgs = params.args; + var setCfg = params.setCfg || {}; + if (!setArgs || setArgs.length < 1) { + if (cm) { + showConfirm(cm, "Invalid mapping: " + params.input); + } + return; + } + var expr = setArgs[0].split("="); + var optionName = expr[0]; + var value = expr[1]; + var forceGet = false; + if (optionName.charAt(optionName.length - 1) == "?") { + if (value) { + throw Error("Trailing characters: " + params.argString); + } + optionName = optionName.substring(0, optionName.length - 1); + forceGet = true; + } + if (value === void 0 && optionName.substring(0, 2) == "no") { + optionName = optionName.substring(2); + value = false; + } + var optionIsBoolean = options[optionName] && options[optionName].type == "boolean"; + if (optionIsBoolean && value == void 0) { + value = true; + } + if (!optionIsBoolean && value === void 0 || forceGet) { + var oldValue = getOption(optionName, cm, setCfg); + if (oldValue instanceof Error) { + showConfirm(cm, oldValue.message); + } else if (oldValue === true || oldValue === false) { + showConfirm(cm, " " + (oldValue ? "" : "no") + optionName); + } else { + showConfirm(cm, " " + optionName + "=" + oldValue); + } + } else { + var setOptionReturn = setOption(optionName, value, cm, setCfg); + if (setOptionReturn instanceof Error) { + showConfirm(cm, setOptionReturn.message); + } + } + }, + setlocal: function(cm, params) { + params.setCfg = { scope: "local" }; + this.set(cm, params); + }, + setglobal: function(cm, params) { + params.setCfg = { scope: "global" }; + this.set(cm, params); + }, + registers: function(cm, params) { + var regArgs = params.args; + var registers = vimGlobalState.registerController.registers; + var regInfo = "----------Registers----------\n\n"; + if (!regArgs) { + for (var registerName in registers) { + var text = registers[registerName].toString(); + if (text.length) { + regInfo += '"' + registerName + " " + text + "\n"; + } + } + } else { + var registerName; + regArgs = regArgs.join(""); + for (var i = 0; i < regArgs.length; i++) { + registerName = regArgs.charAt(i); + if (!vimGlobalState.registerController.isValidRegister(registerName)) { + continue; + } + var register = registers[registerName] || new Register(); + regInfo += '"' + registerName + " " + register.toString() + "\n"; + } + } + showConfirm(cm, regInfo); + }, + sort: function(cm, params) { + var reverse, ignoreCase, unique, number, pattern; + function parseArgs() { + if (params.argString) { + var args = new CodeMirror2.StringStream(params.argString); + if (args.eat("!")) { + reverse = true; + } + if (args.eol()) { + return; + } + if (!args.eatSpace()) { + return "Invalid arguments"; + } + var opts = args.match(/([dinuox]+)?\s*(\/.+\/)?\s*/); + if (!opts && !args.eol()) { + return "Invalid arguments"; + } + if (opts[1]) { + ignoreCase = opts[1].indexOf("i") != -1; + unique = opts[1].indexOf("u") != -1; + var decimal = opts[1].indexOf("d") != -1 || opts[1].indexOf("n") != -1 && 1; + var hex = opts[1].indexOf("x") != -1 && 1; + var octal = opts[1].indexOf("o") != -1 && 1; + if (decimal + hex + octal > 1) { + return "Invalid arguments"; + } + number = decimal && "decimal" || hex && "hex" || octal && "octal"; + } + if (opts[2]) { + pattern = new RegExp(opts[2].substr(1, opts[2].length - 2), ignoreCase ? "i" : ""); + } + } + } + var err = parseArgs(); + if (err) { + showConfirm(cm, err + ": " + params.argString); + return; + } + var lineStart = params.line || cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + if (lineStart == lineEnd) { + return; + } + var curStart = new Pos2(lineStart, 0); + var curEnd = new Pos2(lineEnd, lineLength(cm, lineEnd)); + var text = cm.getRange(curStart, curEnd).split("\n"); + var numberRegex2 = pattern ? pattern : number == "decimal" ? /(-?)([\d]+)/ : number == "hex" ? /(-?)(?:0x)?([0-9a-f]+)/i : number == "octal" ? /([0-7]+)/ : null; + var radix = number == "decimal" ? 10 : number == "hex" ? 16 : number == "octal" ? 8 : null; + var numPart = [], textPart = []; + if (number || pattern) { + for (var i = 0; i < text.length; i++) { + var matchPart = pattern ? text[i].match(pattern) : null; + if (matchPart && matchPart[0] != "") { + numPart.push(matchPart); + } else if (!pattern && numberRegex2.exec(text[i])) { + numPart.push(text[i]); + } else { + textPart.push(text[i]); + } + } + } else { + textPart = text; + } + function compareFn(a, b) { + if (reverse) { + var tmp; + tmp = a; + a = b; + b = tmp; + } + if (ignoreCase) { + a = a.toLowerCase(); + b = b.toLowerCase(); + } + var anum = number && numberRegex2.exec(a); + var bnum = number && numberRegex2.exec(b); + if (!anum) { + return a < b ? -1 : 1; + } + anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix); + bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix); + return anum - bnum; + } + function comparePatternFn(a, b) { + if (reverse) { + var tmp; + tmp = a; + a = b; + b = tmp; + } + if (ignoreCase) { + a[0] = a[0].toLowerCase(); + b[0] = b[0].toLowerCase(); + } + return a[0] < b[0] ? -1 : 1; + } + numPart.sort(pattern ? comparePatternFn : compareFn); + if (pattern) { + for (var i = 0; i < numPart.length; i++) { + numPart[i] = numPart[i].input; + } + } else if (!number) { + textPart.sort(compareFn); + } + text = !reverse ? textPart.concat(numPart) : numPart.concat(textPart); + if (unique) { + var textOld = text; + var lastLine; + text = []; + for (var i = 0; i < textOld.length; i++) { + if (textOld[i] != lastLine) { + text.push(textOld[i]); + } + lastLine = textOld[i]; + } + } + cm.replaceRange(text.join("\n"), curStart, curEnd); + }, + vglobal: function(cm, params) { + this.global(cm, params); + }, + normal: function(cm, params) { + var argString = params.argString && params.argString.trimStart(); + if (!argString) { + showConfirm(cm, "Argument is required."); + return; + } + var line = params.line; + if (typeof line == "number") { + var lineEnd = isNaN(params.lineEnd) ? line : params.lineEnd; + for (var i = line; i <= lineEnd; i++) { + cm.setCursor(i, 0); + doKeyToKey(cm, params.argString.trimStart()); + if (cm.state.vim.insertMode) { + exitInsertMode(cm, true); + } + } + } else { + doKeyToKey(cm, params.argString.trimStart()); + if (cm.state.vim.insertMode) { + exitInsertMode(cm, true); + } + } + }, + global: function(cm, params) { + var argString = params.argString; + if (!argString) { + showConfirm(cm, "Regular Expression missing from global"); + return; + } + var inverted = params.commandName[0] === "v"; + if (argString[0] === "!" && params.commandName[0] === "g") { + inverted = true; + argString = argString.slice(1); + } + var lineStart = params.line !== void 0 ? params.line : cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + var tokens = splitBySlash(argString); + var regexPart = argString, cmd; + if (tokens.length) { + regexPart = tokens[0]; + cmd = tokens.slice(1, tokens.length).join("/"); + } + if (regexPart) { + try { + updateSearchQuery( + cm, + regexPart, + true, + true + /** smartCase */ + ); + } catch (e) { + showConfirm(cm, "Invalid regex: " + regexPart); + return; + } + } + var query = getSearchState(cm).getQuery(); + var matchedLines = []; + for (var i = lineStart; i <= lineEnd; i++) { + var line = cm.getLine(i); + var matched = query.test(line); + if (matched !== inverted) { + matchedLines.push(cmd ? cm.getLineHandle(i) : line); + } + } + if (!cmd) { + showConfirm(cm, matchedLines.join("\n")); + return; + } + var index = 0; + var nextCommand = function() { + if (index < matchedLines.length) { + var lineHandle = matchedLines[index++]; + var lineNum = cm.getLineNumber(lineHandle); + if (lineNum == null) { + nextCommand(); + return; + } + var command = lineNum + 1 + cmd; + exCommandDispatcher.processCommand(cm, command, { + callback: nextCommand + }); + } else if (cm.releaseLineHandles) { + cm.releaseLineHandles(); + } + }; + nextCommand(); + }, + substitute: function(cm, params) { + if (!cm.getSearchCursor) { + throw new Error("Search feature not available. Requires searchcursor.js or any other getSearchCursor implementation."); + } + var argString = params.argString; + var tokens = argString ? splitBySeparator(argString, argString[0]) : []; + var regexPart, replacePart = "", trailing, flagsPart, count; + var confirm = false; + var global = false; + if (tokens.length) { + regexPart = tokens[0]; + if (getOption("pcre") && regexPart !== "") { + regexPart = new RegExp(regexPart).source; + } + replacePart = tokens[1]; + if (replacePart !== void 0) { + if (getOption("pcre")) { + replacePart = unescapeRegexReplace(replacePart.replace(/([^\\])&/g, "$1$$&")); + } else { + replacePart = translateRegexReplace(replacePart); + } + vimGlobalState.lastSubstituteReplacePart = replacePart; + } + trailing = tokens[2] ? tokens[2].split(" ") : []; + } else { + if (argString && argString.length) { + showConfirm(cm, "Substitutions should be of the form :s/pattern/replace/"); + return; + } + } + if (trailing) { + flagsPart = trailing[0]; + count = parseInt(trailing[1]); + if (flagsPart) { + if (flagsPart.indexOf("c") != -1) { + confirm = true; + } + if (flagsPart.indexOf("g") != -1) { + global = true; + } + if (getOption("pcre")) { + regexPart = regexPart + "/" + flagsPart; + } else { + regexPart = regexPart.replace(/\//g, "\\/") + "/" + flagsPart; + } + } + } + if (regexPart) { + try { + updateSearchQuery( + cm, + regexPart, + true, + true + /** smartCase */ + ); + } catch (e) { + showConfirm(cm, "Invalid regex: " + regexPart); + return; + } + } + replacePart = replacePart || vimGlobalState.lastSubstituteReplacePart; + if (replacePart === void 0) { + showConfirm(cm, "No previous substitute regular expression"); + return; + } + var state = getSearchState(cm); + var query = state.getQuery(); + var lineStart = params.line !== void 0 ? params.line : cm.getCursor().line; + var lineEnd = params.lineEnd || lineStart; + if (lineStart == cm.firstLine() && lineEnd == cm.lastLine()) { + lineEnd = Infinity; + } + if (count) { + lineStart = lineEnd; + lineEnd = lineStart + count - 1; + } + var startPos = clipCursorToContent(cm, new Pos2(lineStart, 0)); + var cursor = cm.getSearchCursor(query, startPos); + doReplace(cm, confirm, global, lineStart, lineEnd, cursor, query, replacePart, params.callback); + }, + redo: CodeMirror2.commands.redo, + undo: CodeMirror2.commands.undo, + write: function(cm) { + if (CodeMirror2.commands.save) { + CodeMirror2.commands.save(cm); + } else if (cm.save) { + cm.save(); + } + }, + nohlsearch: function(cm) { + clearSearchHighlight(cm); + }, + yank: function(cm) { + var cur2 = copyCursor(cm.getCursor()); + var line = cur2.line; + var lineText = cm.getLine(line); + vimGlobalState.registerController.pushText( + "0", + "yank", + lineText, + true, + true + ); + }, + delete: function(cm, params) { + var line = params.selectionLine; + var lineEnd = isNaN(params.selectionLineEnd) ? line : params.selectionLineEnd; + operators.delete(cm, { linewise: true }, [ + { + anchor: new Pos2(line, 0), + head: new Pos2(lineEnd + 1, 0) + } + ]); + }, + join: function(cm, params) { + var line = params.selectionLine; + var lineEnd = isNaN(params.selectionLineEnd) ? line : params.selectionLineEnd; + cm.setCursor(new Pos2(line, 0)); + actions.joinLines(cm, { repeat: lineEnd - line }, cm.state.vim); + }, + delmarks: function(cm, params) { + if (!params.argString || !trim(params.argString)) { + showConfirm(cm, "Argument required"); + return; + } + var state = cm.state.vim; + var stream = new CodeMirror2.StringStream(trim(params.argString)); + while (!stream.eol()) { + stream.eatSpace(); + var count = stream.pos; + if (!stream.match(/[a-zA-Z]/, false)) { + showConfirm(cm, "Invalid argument: " + params.argString.substring(count)); + return; + } + var sym = stream.next(); + if (stream.match("-", true)) { + if (!stream.match(/[a-zA-Z]/, false)) { + showConfirm(cm, "Invalid argument: " + params.argString.substring(count)); + return; + } + var startMark = sym; + var finishMark = stream.next(); + if (isLowerCase(startMark) && isLowerCase(finishMark) || isUpperCase(startMark) && isUpperCase(finishMark)) { + var start = startMark.charCodeAt(0); + var finish = finishMark.charCodeAt(0); + if (start >= finish) { + showConfirm(cm, "Invalid argument: " + params.argString.substring(count)); + return; + } + for (var j = 0; j <= finish - start; j++) { + var mark = String.fromCharCode(start + j); + delete state.marks[mark]; + } + } else { + showConfirm(cm, "Invalid argument: " + startMark + "-"); + return; + } + } else { + delete state.marks[sym]; + } + } + } + }; + var exCommandDispatcher = new ExCommandDispatcher(); + function doReplace(cm, confirm, global, lineStart, lineEnd, searchCursor, query, replaceWith, callback) { + cm.state.vim.exMode = true; + var done = false; + var lastPos, modifiedLineNumber, joined; + function replaceAll() { + cm.operation(function() { + while (!done) { + replace(); + next(); + } + stop(); + }); + } + function replace() { + var text = cm.getRange(searchCursor.from(), searchCursor.to()); + var newText = text.replace(query, replaceWith); + var unmodifiedLineNumber = searchCursor.to().line; + searchCursor.replace(newText); + modifiedLineNumber = searchCursor.to().line; + lineEnd += modifiedLineNumber - unmodifiedLineNumber; + joined = modifiedLineNumber < unmodifiedLineNumber; + } + function findNextValidMatch() { + var lastMatchTo = lastPos && copyCursor(searchCursor.to()); + var match = searchCursor.findNext(); + if (match && !match[0] && lastMatchTo && cursorEqual(searchCursor.from(), lastMatchTo)) { + match = searchCursor.findNext(); + } + return match; + } + function next() { + while (findNextValidMatch() && isInRange(searchCursor.from(), lineStart, lineEnd)) { + if (!global && searchCursor.from().line == modifiedLineNumber && !joined) { + continue; + } + cm.scrollIntoView(searchCursor.from(), 30); + cm.setSelection(searchCursor.from(), searchCursor.to()); + lastPos = searchCursor.from(); + done = false; + return; + } + done = true; + } + function stop(close) { + if (close) { + close(); + } + cm.focus(); + if (lastPos) { + cm.setCursor(lastPos); + var vim2 = cm.state.vim; + vim2.exMode = false; + vim2.lastHPos = vim2.lastHSPos = lastPos.ch; + } + if (callback) { + callback(); + } + } + function onPromptKeyDown(e, _value, close) { + CodeMirror2.e_stop(e); + var keyName = CodeMirror2.keyName(e); + switch (keyName) { + case "Y": + replace(); + next(); + break; + case "N": + next(); + break; + case "A": + var savedCallback = callback; + callback = void 0; + cm.operation(replaceAll); + callback = savedCallback; + break; + case "L": + replace(); + case "Q": + case "Esc": + case "Ctrl-C": + case "Ctrl-[": + stop(close); + break; + } + if (done) { + stop(close); + } + return true; + } + next(); + if (done) { + showConfirm(cm, "No matches for " + query.source); + return; + } + if (!confirm) { + replaceAll(); + if (callback) { + callback(); + } + return; + } + showPrompt(cm, { + prefix: dom("span", "replace with ", dom("strong", replaceWith), " (y/n/a/q/l)"), + onKeyDown: onPromptKeyDown + }); + } + CodeMirror2.keyMap.vim = { + attach: attachVimMap, + detach: detachVimMap, + call: cmKey + }; + function exitInsertMode(cm, keepCursor) { + var vim2 = cm.state.vim; + var macroModeState = vimGlobalState.macroModeState; + var insertModeChangeRegister = vimGlobalState.registerController.getRegister("."); + var isPlaying = macroModeState.isPlaying; + var lastChange = macroModeState.lastInsertModeChanges; + if (!isPlaying) { + cm.off("change", onChange); + if (vim2.insertEnd) + vim2.insertEnd.clear(); + vim2.insertEnd = null; + CodeMirror2.off(cm.getInputField(), "keydown", onKeyEventTargetKeyDown); + } + if (!isPlaying && vim2.insertModeRepeat > 1) { + repeatLastEdit( + cm, + vim2, + vim2.insertModeRepeat - 1, + true + /** repeatForInsert */ + ); + vim2.lastEditInputState.repeatOverride = vim2.insertModeRepeat; + } + delete vim2.insertModeRepeat; + vim2.insertMode = false; + if (!keepCursor) { + cm.setCursor(cm.getCursor().line, cm.getCursor().ch - 1); + } + cm.setOption("keyMap", "vim"); + cm.setOption("disableInput", true); + cm.toggleOverwrite(false); + insertModeChangeRegister.setText(lastChange.changes.join("")); + CodeMirror2.signal(cm, "vim-mode-change", { mode: "normal" }); + if (macroModeState.isRecording) { + logInsertModeChange(macroModeState); + } + } + function _mapCommand(command) { + defaultKeymap2.unshift(command); + } + function mapCommand(keys2, type, name, args, extra) { + var command = { keys: keys2, type }; + command[type] = name; + command[type + "Args"] = args; + for (var key in extra) + command[key] = extra[key]; + _mapCommand(command); + } + defineOption("insertModeEscKeysTimeout", 200, "number"); + CodeMirror2.keyMap["vim-insert"] = { + // TODO: override navigation keys so that Esc will cancel automatic + // indentation from o, O, i_ + fallthrough: ["default"], + attach: attachVimMap, + detach: detachVimMap, + call: cmKey + }; + CodeMirror2.keyMap["vim-replace"] = { + "Backspace": "goCharLeft", + fallthrough: ["vim-insert"], + attach: attachVimMap, + detach: detachVimMap + }; + function executeMacroRegister(cm, vim2, macroModeState, registerName) { + var register = vimGlobalState.registerController.getRegister(registerName); + if (registerName == ":") { + if (register.keyBuffer[0]) { + exCommandDispatcher.processCommand(cm, register.keyBuffer[0]); + } + macroModeState.isPlaying = false; + return; + } + var keyBuffer = register.keyBuffer; + var imc = 0; + macroModeState.isPlaying = true; + macroModeState.replaySearchQueries = register.searchQueries.slice(0); + for (var i = 0; i < keyBuffer.length; i++) { + var text = keyBuffer[i]; + var match, key; + while (text) { + match = /<\w+-.+?>|<\w+>|./.exec(text); + key = match[0]; + text = text.substring(match.index + key.length); + vimApi.handleKey(cm, key, "macro"); + if (vim2.insertMode) { + var changes = register.insertModeChanges[imc++].changes; + vimGlobalState.macroModeState.lastInsertModeChanges.changes = changes; + repeatInsertModeChanges(cm, changes, 1); + exitInsertMode(cm); + } + } + } + macroModeState.isPlaying = false; + } + function logKey(macroModeState, key) { + if (macroModeState.isPlaying) { + return; + } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register) { + register.pushText(key); + } + } + function logInsertModeChange(macroModeState) { + if (macroModeState.isPlaying) { + return; + } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register && register.pushInsertModeChanges) { + register.pushInsertModeChanges(macroModeState.lastInsertModeChanges); + } + } + function logSearchQuery(macroModeState, query) { + if (macroModeState.isPlaying) { + return; + } + var registerName = macroModeState.latestRegister; + var register = vimGlobalState.registerController.getRegister(registerName); + if (register && register.pushSearchQuery) { + register.pushSearchQuery(query); + } + } + function onChange(cm, changeObj) { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + if (!macroModeState.isPlaying) { + var vim2 = cm.state.vim; + while (changeObj) { + lastChange.expectCursorActivityForChange = true; + if (lastChange.ignoreCount > 1) { + lastChange.ignoreCount--; + } else if (changeObj.origin == "+input" || changeObj.origin == "paste" || changeObj.origin === void 0) { + var selectionCount = cm.listSelections().length; + if (selectionCount > 1) + lastChange.ignoreCount = selectionCount; + var text = changeObj.text.join("\n"); + if (lastChange.maybeReset) { + lastChange.changes = []; + lastChange.maybeReset = false; + } + if (text) { + if (cm.state.overwrite && !/\n/.test(text)) { + lastChange.changes.push([text]); + } else { + if (text.length > 1) { + var insertEnd = vim2 && vim2.insertEnd && vim2.insertEnd.find(); + var cursor = cm.getCursor(); + if (insertEnd && insertEnd.line == cursor.line) { + var offset = insertEnd.ch - cursor.ch; + if (offset > 0 && offset < text.length) { + lastChange.changes.push([text, offset]); + text = ""; + } + } + } + if (text) + lastChange.changes.push(text); + } + } + } + changeObj = changeObj.next; + } + } + } + function onCursorActivity(cm) { + var vim2 = cm.state.vim; + if (vim2.insertMode) { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.isPlaying) { + return; + } + var lastChange = macroModeState.lastInsertModeChanges; + if (lastChange.expectCursorActivityForChange) { + lastChange.expectCursorActivityForChange = false; + } else { + lastChange.maybeReset = true; + if (vim2.insertEnd) + vim2.insertEnd.clear(); + vim2.insertEnd = cm.setBookmark(cm.getCursor(), { insertLeft: true }); + } + } else if (!cm.curOp.isVimOp) { + handleExternalSelection(cm, vim2); + } + } + function handleExternalSelection(cm, vim2) { + var anchor = cm.getCursor("anchor"); + var head = cm.getCursor("head"); + if (vim2.visualMode && !cm.somethingSelected()) { + exitVisualMode(cm, false); + } else if (!vim2.visualMode && !vim2.insertMode && cm.somethingSelected()) { + vim2.visualMode = true; + vim2.visualLine = false; + CodeMirror2.signal(cm, "vim-mode-change", { mode: "visual" }); + } + if (vim2.visualMode) { + var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0; + var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0; + head = offsetCursor(head, 0, headOffset); + anchor = offsetCursor(anchor, 0, anchorOffset); + vim2.sel = { + anchor, + head + }; + updateMark(cm, vim2, "<", cursorMin(head, anchor)); + updateMark(cm, vim2, ">", cursorMax(head, anchor)); + } else if (!vim2.insertMode) { + vim2.lastHPos = cm.getCursor().ch; + } + } + function InsertModeKey(keyName) { + this.keyName = keyName; + } + function onKeyEventTargetKeyDown(e) { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + var keyName = CodeMirror2.keyName(e); + if (!keyName) { + return; + } + function onKeyFound() { + if (lastChange.maybeReset) { + lastChange.changes = []; + lastChange.maybeReset = false; + } + lastChange.changes.push(new InsertModeKey(keyName)); + return true; + } + if (keyName.indexOf("Delete") != -1 || keyName.indexOf("Backspace") != -1) { + CodeMirror2.lookupKey(keyName, "vim-insert", onKeyFound); + } + } + function repeatLastEdit(cm, vim2, repeat, repeatForInsert) { + var macroModeState = vimGlobalState.macroModeState; + macroModeState.isPlaying = true; + var isAction = !!vim2.lastEditActionCommand; + var cachedInputState = vim2.inputState; + function repeatCommand() { + if (isAction) { + commandDispatcher.processAction(cm, vim2, vim2.lastEditActionCommand); + } else { + commandDispatcher.evalInput(cm, vim2); + } + } + function repeatInsert(repeat2) { + if (macroModeState.lastInsertModeChanges.changes.length > 0) { + repeat2 = !vim2.lastEditActionCommand ? 1 : repeat2; + var changeObject = macroModeState.lastInsertModeChanges; + repeatInsertModeChanges(cm, changeObject.changes, repeat2); + } + } + vim2.inputState = vim2.lastEditInputState; + if (isAction && vim2.lastEditActionCommand.interlaceInsertRepeat) { + for (var i = 0; i < repeat; i++) { + repeatCommand(); + repeatInsert(1); + } + } else { + if (!repeatForInsert) { + repeatCommand(); + } + repeatInsert(repeat); + } + vim2.inputState = cachedInputState; + if (vim2.insertMode && !repeatForInsert) { + exitInsertMode(cm); + } + macroModeState.isPlaying = false; + } + function sendCmKey(cm, key) { + CodeMirror2.lookupKey(key, "vim-insert", function keyHandler(binding) { + if (typeof binding == "string") { + CodeMirror2.commands[binding](cm); + } else { + binding(cm); + } + return true; + }); + } + function repeatInsertModeChanges(cm, changes, repeat) { + var head = cm.getCursor("head"); + var visualBlock = vimGlobalState.macroModeState.lastInsertModeChanges.visualBlock; + if (visualBlock) { + selectForInsert(cm, head, visualBlock + 1); + repeat = cm.listSelections().length; + cm.setCursor(head); + } + for (var i = 0; i < repeat; i++) { + if (visualBlock) { + cm.setCursor(offsetCursor(head, i, 0)); + } + for (var j = 0; j < changes.length; j++) { + var change = changes[j]; + if (change instanceof InsertModeKey) { + sendCmKey(cm, change.keyName); + } else if (typeof change == "string") { + cm.replaceSelection(change); + } else { + var start = cm.getCursor(); + var end = offsetCursor(start, 0, change[0].length - (change[1] || 0)); + cm.replaceRange(change[0], start, change[1] ? start : end); + cm.setCursor(end); + } + } + } + if (visualBlock) { + cm.setCursor(offsetCursor(head, 0, 1)); + } + } + function cloneVimState(state) { + var n = new state.constructor(); + Object.keys(state).forEach(function(key) { + if (key == "insertEnd") + return; + var o = state[key]; + if (Array.isArray(o)) + o = o.slice(); + else if (o && typeof o == "object" && o.constructor != Object) + o = cloneVimState(o); + n[key] = o; + }); + if (state.sel) { + n.sel = { + head: state.sel.head && copyCursor(state.sel.head), + anchor: state.sel.anchor && copyCursor(state.sel.anchor) + }; + } + return n; + } + function multiSelectHandleKey(cm, key, origin) { + var isHandled = false; + var vim2 = vimApi.maybeInitVimState_(cm); + var visualBlock = vim2.visualBlock || vim2.wasInVisualBlock; + var wasMultiselect = cm.isInMultiSelectMode(); + if (vim2.wasInVisualBlock && !wasMultiselect) { + vim2.wasInVisualBlock = false; + } else if (wasMultiselect && vim2.visualBlock) { + vim2.wasInVisualBlock = true; + } + if (key == "" && !vim2.insertMode && !vim2.visualMode && wasMultiselect && vim2.status == "") { + clearInputState(cm); + } else if (visualBlock || !wasMultiselect || cm.inVirtualSelectionMode) { + isHandled = vimApi.handleKey(cm, key, origin); + } else { + var old = cloneVimState(vim2); + var changeQueueList = vim2.inputState.changeQueueList || []; + cm.operation(function() { + cm.curOp.isVimOp = true; + var index = 0; + cm.forEachSelection(function() { + cm.state.vim.inputState.changeQueue = changeQueueList[index]; + var head = cm.getCursor("head"); + var anchor = cm.getCursor("anchor"); + var headOffset = !cursorIsBefore(head, anchor) ? -1 : 0; + var anchorOffset = cursorIsBefore(head, anchor) ? -1 : 0; + head = offsetCursor(head, 0, headOffset); + anchor = offsetCursor(anchor, 0, anchorOffset); + cm.state.vim.sel.head = head; + cm.state.vim.sel.anchor = anchor; + isHandled = vimApi.handleKey(cm, key, origin); + if (cm.virtualSelection) { + changeQueueList[index] = cm.state.vim.inputState.changeQueue; + cm.state.vim = cloneVimState(old); + } + index++; + }); + if (cm.curOp.cursorActivity && !isHandled) + cm.curOp.cursorActivity = false; + cm.state.vim = vim2; + vim2.inputState.changeQueueList = changeQueueList; + vim2.inputState.changeQueue = null; + }, true); + } + if (isHandled && !vim2.visualMode && !vim2.insert && vim2.visualMode != cm.somethingSelected()) { + handleExternalSelection(cm, vim2); + } + return isHandled; + } + resetVimGlobalState(); + return vimApi; +} +function indexFromPos(doc, pos) { + var ch = pos.ch; + var lineNumber = pos.line + 1; + if (lineNumber < 1) { + lineNumber = 1; + ch = 0; + } + if (lineNumber > doc.lines) { + lineNumber = doc.lines; + ch = Number.MAX_VALUE; + } + var line = doc.line(lineNumber); + return Math.min(line.from + Math.max(0, ch), line.to); +} +function posFromIndex(doc, offset) { + let line = doc.lineAt(offset); + return { line: line.number - 1, ch: offset - line.from }; +} +var Pos = class { + constructor(line, ch) { + this.line = line; + this.ch = ch; + } +}; +function on(emitter, type, f) { + if (emitter.addEventListener) { + emitter.addEventListener(type, f, false); + } else { + var map = emitter._handlers || (emitter._handlers = {}); + map[type] = (map[type] || []).concat(f); + } +} +function off(emitter, type, f) { + if (emitter.removeEventListener) { + emitter.removeEventListener(type, f, false); + } else { + var map = emitter._handlers, arr = map && map[type]; + if (arr) { + var index = arr.indexOf(f); + if (index > -1) { + map[type] = arr.slice(0, index).concat(arr.slice(index + 1)); + } + } + } +} +function signal(emitter, type, ...args) { + var _a; + var handlers = (_a = emitter._handlers) === null || _a === void 0 ? void 0 : _a[type]; + if (!handlers) + return; + for (var i = 0; i < handlers.length; ++i) { + handlers[i](...args); + } +} +function signalTo(handlers, ...args) { + if (!handlers) + return; + for (var i = 0; i < handlers.length; ++i) { + handlers[i](...args); + } +} +var specialKey = { + Return: "CR", + Backspace: "BS", + "Delete": "Del", + Escape: "Esc", + Insert: "Ins", + ArrowLeft: "Left", + ArrowRight: "Right", + ArrowUp: "Up", + ArrowDown: "Down", + Enter: "CR", + " ": "Space" +}; +var ignoredKeys = { + Shift: 1, + Alt: 1, + Command: 1, + Control: 1, + CapsLock: 1, + AltGraph: 1, + Dead: 1, + Unidentified: 1 +}; +var wordChar; +try { + wordChar = /* @__PURE__ */ new RegExp("[\\w\\p{Alphabetic}\\p{Number}_]", "u"); +} catch (_) { + wordChar = /[\w]/; +} +function dispatchChange(cm, transaction) { + var view = cm.cm6; + var type = "input.type.compose"; + if (cm.curOp) { + if (!cm.curOp.lastChange) + type = "input.type.compose.start"; + } + if (transaction.annotations) { + try { + transaction.annotations.some(function(note) { + if (note.value == "input") + note.value = type; + }); + } catch (e) { + console.error(e); + } + } else { + transaction.userEvent = type; + } + return view.dispatch(transaction); +} +function runHistoryCommand(cm, revert) { + var _a; + if (cm.curOp) { + cm.curOp.$changeStart = void 0; + } + (revert ? import_commands.undo : import_commands.redo)(cm.cm6); + let changeStartIndex = (_a = cm.curOp) === null || _a === void 0 ? void 0 : _a.$changeStart; + if (changeStartIndex != null) { + cm.cm6.dispatch({ selection: { anchor: changeStartIndex } }); + } +} +var keys = {}; +var CodeMirror = class { + constructor(cm6) { + this.state = {}; + this.marks = /* @__PURE__ */ Object.create(null); + this.$mid = 0; + this.options = {}; + this._handlers = {}; + this.$lastChangeEndOffset = 0; + this.virtualSelection = null; + this.cm6 = cm6; + this.onChange = this.onChange.bind(this); + this.onSelectionChange = this.onSelectionChange.bind(this); + } + // -------------------------- + openDialog(template, callback, options) { + return openDialog(this, template, callback, options); + } + openNotification(template, options) { + return openNotification(this, template, options); + } + on(type, f) { + on(this, type, f); + } + off(type, f) { + off(this, type, f); + } + signal(type, e, handlers) { + signal(this, type, e, handlers); + } + indexFromPos(pos) { + return indexFromPos(this.cm6.state.doc, pos); + } + posFromIndex(offset) { + return posFromIndex(this.cm6.state.doc, offset); + } + foldCode(pos) { + let view = this.cm6; + let ranges = view.state.selection.ranges; + let doc = this.cm6.state.doc; + let index = indexFromPos(doc, pos); + let tmpRanges = import_state.EditorSelection.create([import_state.EditorSelection.range(index, index)], 0).ranges; + view.state.selection.ranges = tmpRanges; + (0, import_language.foldCode)(view); + view.state.selection.ranges = ranges; + } + firstLine() { + return 0; + } + lastLine() { + return this.cm6.state.doc.lines - 1; + } + lineCount() { + return this.cm6.state.doc.lines; + } + setCursor(line, ch) { + if (typeof line === "object") { + ch = line.ch; + line = line.line; + } + var offset = indexFromPos(this.cm6.state.doc, { line, ch }); + this.cm6.dispatch({ selection: { anchor: offset } }, { scrollIntoView: !this.curOp }); + if (this.curOp && !this.curOp.isVimOp) + this.onBeforeEndOperation(); + } + getCursor(p) { + var sel = this.cm6.state.selection.main; + var offset = p == "head" || !p ? sel.head : p == "anchor" ? sel.anchor : p == "start" ? sel.from : p == "end" ? sel.to : null; + if (offset == null) + throw new Error("Invalid cursor type"); + return this.posFromIndex(offset); + } + listSelections() { + var doc = this.cm6.state.doc; + return this.cm6.state.selection.ranges.map((r) => { + return { + anchor: posFromIndex(doc, r.anchor), + head: posFromIndex(doc, r.head) + }; + }); + } + setSelections(p, primIndex) { + var doc = this.cm6.state.doc; + var ranges = p.map((x) => { + return import_state.EditorSelection.range(indexFromPos(doc, x.anchor), indexFromPos(doc, x.head)); + }); + this.cm6.dispatch({ + selection: import_state.EditorSelection.create(ranges, primIndex) + }); + } + setSelection(anchor, head, options) { + var doc = this.cm6.state.doc; + var ranges = [import_state.EditorSelection.range(indexFromPos(doc, anchor), indexFromPos(doc, head))]; + this.cm6.dispatch({ + selection: import_state.EditorSelection.create(ranges, 0) + }); + if (options && options.origin == "*mouse") { + this.onBeforeEndOperation(); + } + } + getLine(row) { + var doc = this.cm6.state.doc; + if (row < 0 || row >= doc.lines) + return ""; + return this.cm6.state.doc.line(row + 1).text; + } + getLineHandle(row) { + if (!this.$lineHandleChanges) + this.$lineHandleChanges = []; + return { row, index: this.indexFromPos(new Pos(row, 0)) }; + } + getLineNumber(handle) { + var updates = this.$lineHandleChanges; + if (!updates) + return null; + var offset = handle.index; + for (var i = 0; i < updates.length; i++) { + offset = updates[i].changes.mapPos(offset, 1, import_state.MapMode.TrackAfter); + if (offset == null) + return null; + } + var pos = this.posFromIndex(offset); + return pos.ch == 0 ? pos.line : null; + } + releaseLineHandles() { + this.$lineHandleChanges = void 0; + } + getRange(s, e) { + var doc = this.cm6.state.doc; + return this.cm6.state.sliceDoc(indexFromPos(doc, s), indexFromPos(doc, e)); + } + replaceRange(text, s, e) { + if (!e) + e = s; + var doc = this.cm6.state.doc; + var from = indexFromPos(doc, s); + var to = indexFromPos(doc, e); + dispatchChange(this, { changes: { from, to, insert: text } }); + } + replaceSelection(text) { + dispatchChange(this, this.cm6.state.replaceSelection(text)); + } + replaceSelections(replacements) { + var ranges = this.cm6.state.selection.ranges; + var changes = ranges.map((r, i) => { + return { from: r.from, to: r.to, insert: replacements[i] || "" }; + }); + dispatchChange(this, { changes }); + } + getSelection() { + return this.getSelections().join("\n"); + } + getSelections() { + var cm = this.cm6; + return cm.state.selection.ranges.map((r) => cm.state.sliceDoc(r.from, r.to)); + } + somethingSelected() { + return this.cm6.state.selection.ranges.some((r) => !r.empty); + } + getInputField() { + return this.cm6.contentDOM; + } + clipPos(p) { + var doc = this.cm6.state.doc; + var ch = p.ch; + var lineNumber = p.line + 1; + if (lineNumber < 1) { + lineNumber = 1; + ch = 0; + } + if (lineNumber > doc.lines) { + lineNumber = doc.lines; + ch = Number.MAX_VALUE; + } + var line = doc.line(lineNumber); + ch = Math.min(Math.max(0, ch), line.to - line.from); + return new Pos(lineNumber - 1, ch); + } + getValue() { + return this.cm6.state.doc.toString(); + } + setValue(text) { + var cm = this.cm6; + return cm.dispatch({ + changes: { from: 0, to: cm.state.doc.length, insert: text }, + selection: import_state.EditorSelection.range(0, 0) + }); + } + focus() { + return this.cm6.focus(); + } + blur() { + return this.cm6.contentDOM.blur(); + } + defaultTextHeight() { + return this.cm6.defaultLineHeight; + } + findMatchingBracket(pos) { + var state = this.cm6.state; + var offset = indexFromPos(state.doc, pos); + var m = (0, import_language.matchBrackets)(state, offset + 1, -1); + if (m && m.end) { + return { to: posFromIndex(state.doc, m.end.from) }; + } + m = (0, import_language.matchBrackets)(state, offset, 1); + if (m && m.end) { + return { to: posFromIndex(state.doc, m.end.from) }; + } + return { to: void 0 }; + } + scanForBracket(pos, dir, style, config3) { + return scanForBracket(this, pos, dir, style, config3); + } + indentLine(line, more) { + if (more) + this.indentMore(); + else + this.indentLess(); + } + indentMore() { + (0, import_commands.indentMore)(this.cm6); + } + indentLess() { + (0, import_commands.indentLess)(this.cm6); + } + execCommand(name) { + if (name == "indentAuto") + CodeMirror.commands.indentAuto(this); + else if (name == "goLineLeft") + (0, import_commands.cursorLineBoundaryBackward)(this.cm6); + else if (name == "goLineRight") { + (0, import_commands.cursorLineBoundaryForward)(this.cm6); + (0, import_commands.cursorCharBackward)(this.cm6); + } else + console.log(name + " is not implemented"); + } + setBookmark(cursor, options) { + var assoc = (options === null || options === void 0 ? void 0 : options.insertLeft) ? 1 : -1; + var offset = this.indexFromPos(cursor); + var bm = new Marker(this, offset, assoc); + return bm; + } + addOverlay({ query }) { + let cm6Query = new import_search.SearchQuery({ + regexp: true, + search: query.source, + caseSensitive: !/i/.test(query.flags) + }); + if (cm6Query.valid) { + cm6Query.forVim = true; + this.cm6Query = cm6Query; + let effect = import_search.setSearchQuery.of(cm6Query); + this.cm6.dispatch({ effects: effect }); + return cm6Query; + } + } + removeOverlay(overlay) { + if (!this.cm6Query) + return; + this.cm6Query.forVim = false; + let effect = import_search.setSearchQuery.of(this.cm6Query); + this.cm6.dispatch({ effects: effect }); + } + getSearchCursor(query, pos) { + var cm = this; + var last = null; + var lastCM5Result = null; + if (pos.ch == void 0) + pos.ch = Number.MAX_VALUE; + var firstOffset = indexFromPos(cm.cm6.state.doc, pos); + var source = query.source.replace(/(\\.|{(?:\d+(?:,\d*)?|,\d+)})|[{}]/g, function(a, b) { + if (!b) + return "\\" + a; + return b; + }); + function rCursor(doc, from = 0, to = doc.length) { + return new import_search.RegExpCursor(doc, source, { ignoreCase: query.ignoreCase }, from, to); + } + function nextMatch(from) { + var doc = cm.cm6.state.doc; + if (from > doc.length) + return null; + let res = rCursor(doc, from).next(); + return res.done ? null : res.value; + } + var ChunkSize = 1e4; + function prevMatchInRange(from, to) { + var doc = cm.cm6.state.doc; + for (let size = 1; ; size++) { + let start = Math.max(from, to - size * ChunkSize); + let cursor = rCursor(doc, start, to), range = null; + while (!cursor.next().done) + range = cursor.value; + if (range && (start == from || range.from > start + 10)) + return range; + if (start == from) + return null; + } + } + return { + findNext: function() { + return this.find(false); + }, + findPrevious: function() { + return this.find(true); + }, + find: function(back) { + var doc = cm.cm6.state.doc; + if (back) { + let endAt = last ? last.from == last.to ? last.to - 1 : last.from : firstOffset; + last = prevMatchInRange(0, endAt); + } else { + let startFrom = last ? last.from == last.to ? last.to + 1 : last.to : firstOffset; + last = nextMatch(startFrom); + } + lastCM5Result = last && { + from: posFromIndex(doc, last.from), + to: posFromIndex(doc, last.to), + match: last.match + }; + return last && last.match; + }, + from: function() { + return lastCM5Result === null || lastCM5Result === void 0 ? void 0 : lastCM5Result.from; + }, + to: function() { + return lastCM5Result === null || lastCM5Result === void 0 ? void 0 : lastCM5Result.to; + }, + replace: function(text) { + if (last) { + dispatchChange(cm, { + changes: { from: last.from, to: last.to, insert: text } + }); + last.to = last.from + text.length; + if (lastCM5Result) { + lastCM5Result.to = posFromIndex(cm.cm6.state.doc, last.to); + } + } + } + }; + } + findPosV(start, amount, unit, goalColumn) { + let { cm6 } = this; + const doc = cm6.state.doc; + let pixels = unit == "page" ? cm6.dom.clientHeight : 0; + const startOffset = indexFromPos(doc, start); + let range = import_state.EditorSelection.range(startOffset, startOffset, goalColumn); + let count = Math.round(Math.abs(amount)); + for (let i = 0; i < count; i++) { + if (unit == "page") { + range = cm6.moveVertically(range, amount > 0, pixels); + } else if (unit == "line") { + range = cm6.moveVertically(range, amount > 0); + } + } + let pos = posFromIndex(doc, range.head); + if (amount < 0 && range.head == 0 && goalColumn != 0 && start.line == 0 && start.ch != 0 || amount > 0 && range.head == doc.length && pos.ch != goalColumn && start.line == pos.line) { + pos.hitSide = true; + } + return pos; + } + charCoords(pos, mode) { + var rect = this.cm6.contentDOM.getBoundingClientRect(); + var offset = indexFromPos(this.cm6.state.doc, pos); + var coords = this.cm6.coordsAtPos(offset); + var d = -rect.top; + return { left: ((coords === null || coords === void 0 ? void 0 : coords.left) || 0) - rect.left, top: ((coords === null || coords === void 0 ? void 0 : coords.top) || 0) + d, bottom: ((coords === null || coords === void 0 ? void 0 : coords.bottom) || 0) + d }; + } + coordsChar(coords, mode) { + var rect = this.cm6.contentDOM.getBoundingClientRect(); + var offset = this.cm6.posAtCoords({ x: coords.left + rect.left, y: coords.top + rect.top }) || 0; + return posFromIndex(this.cm6.state.doc, offset); + } + getScrollInfo() { + var scroller = this.cm6.scrollDOM; + return { + left: scroller.scrollLeft, + top: scroller.scrollTop, + height: scroller.scrollHeight, + width: scroller.scrollWidth, + clientHeight: scroller.clientHeight, + clientWidth: scroller.clientWidth + }; + } + scrollTo(x, y) { + if (x != null) + this.cm6.scrollDOM.scrollLeft = x; + if (y != null) + this.cm6.scrollDOM.scrollTop = y; + } + scrollIntoView(pos, margin) { + if (pos) { + var offset = this.indexFromPos(pos); + this.cm6.dispatch({ + effects: import_view.EditorView.scrollIntoView(offset) + }); + } else { + this.cm6.dispatch({ scrollIntoView: true, userEvent: "scroll" }); + } + } + getWrapperElement() { + return this.cm6.dom; + } + // for tests + getMode() { + return { name: this.getOption("mode") }; + } + setSize(w, h) { + this.cm6.dom.style.width = w + 4 + "px"; + this.cm6.dom.style.height = h + "px"; + this.refresh(); + } + refresh() { + this.cm6.measure(); + } + // event listeners + destroy() { + this.removeOverlay(); + } + getLastEditEnd() { + return this.posFromIndex(this.$lastChangeEndOffset); + } + onChange(update) { + if (this.$lineHandleChanges) { + this.$lineHandleChanges.push(update); + } + for (let i in this.marks) { + let m = this.marks[i]; + m.update(update.changes); + } + if (this.virtualSelection) { + this.virtualSelection.ranges = this.virtualSelection.ranges.map((range) => range.map(update.changes)); + } + var curOp = this.curOp = this.curOp || {}; + update.changes.iterChanges((fromA, toA, fromB, toB, text) => { + if (curOp.$changeStart == null || curOp.$changeStart > fromB) + curOp.$changeStart = fromB; + this.$lastChangeEndOffset = toB; + var change = { text: text.toJSON() }; + if (!curOp.lastChange) { + curOp.lastChange = curOp.change = change; + } else { + curOp.lastChange.next = curOp.lastChange = change; + } + }, true); + if (!curOp.changeHandlers) + curOp.changeHandlers = this._handlers["change"] && this._handlers["change"].slice(); + } + onSelectionChange() { + var curOp = this.curOp = this.curOp || {}; + if (!curOp.cursorActivityHandlers) + curOp.cursorActivityHandlers = this._handlers["cursorActivity"] && this._handlers["cursorActivity"].slice(); + this.curOp.cursorActivity = true; + } + operation(fn) { + if (!this.curOp) + this.curOp = { $d: 0 }; + this.curOp.$d++; + try { + var result = fn(); + } finally { + if (this.curOp) { + this.curOp.$d--; + if (!this.curOp.$d) + this.onBeforeEndOperation(); + } + } + return result; + } + onBeforeEndOperation() { + var op = this.curOp; + var scrollIntoView2 = false; + if (op) { + if (op.change) { + signalTo(op.changeHandlers, this, op.change); + } + if (op && op.cursorActivity) { + signalTo(op.cursorActivityHandlers, this, null); + if (op.isVimOp) + scrollIntoView2 = true; + } + this.curOp = null; + } + if (scrollIntoView2) + this.scrollIntoView(); + } + moveH(increment, unit) { + if (unit == "char") { + var cur2 = this.getCursor(); + this.setCursor(cur2.line, cur2.ch + increment); + } + } + setOption(name, val) { + switch (name) { + case "keyMap": + this.state.keyMap = val; + break; + } + } + getOption(name) { + switch (name) { + case "firstLineNumber": + return 1; + case "tabSize": + return this.cm6.state.tabSize || 4; + case "readonly": + return this.cm6.state.readOnly; + case "indentWithTabs": + return this.cm6.state.facet(import_language.indentUnit) == " "; + case "indentUnit": + return this.cm6.state.facet(import_language.indentUnit).length || 2; + case "keyMap": + return this.state.keyMap || "vim"; + } + } + toggleOverwrite(on2) { + this.state.overwrite = on2; + } + getTokenTypeAt(pos) { + var _a; + var offset = this.indexFromPos(pos); + var tree = (0, import_language.ensureSyntaxTree)(this.cm6.state, offset); + var node = tree === null || tree === void 0 ? void 0 : tree.resolve(offset); + var type = ((_a = node === null || node === void 0 ? void 0 : node.type) === null || _a === void 0 ? void 0 : _a.name) || ""; + if (/comment/i.test(type)) + return "comment"; + if (/string/i.test(type)) + return "string"; + return ""; + } + overWriteSelection(text) { + var doc = this.cm6.state.doc; + var sel = this.cm6.state.selection; + var ranges = sel.ranges.map((x) => { + if (x.empty) { + var ch = x.to < doc.length ? doc.sliceString(x.from, x.to + 1) : ""; + if (ch && !/\n/.test(ch)) + return import_state.EditorSelection.range(x.from, x.to + 1); + } + return x; + }); + this.cm6.dispatch({ + selection: import_state.EditorSelection.create(ranges, sel.mainIndex) + }); + this.replaceSelection(text); + } + /*** multiselect ****/ + isInMultiSelectMode() { + return this.cm6.state.selection.ranges.length > 1; + } + virtualSelectionMode() { + return !!this.virtualSelection; + } + forEachSelection(command) { + var selection = this.cm6.state.selection; + this.virtualSelection = import_state.EditorSelection.create(selection.ranges, selection.mainIndex); + for (var i = 0; i < this.virtualSelection.ranges.length; i++) { + var range = this.virtualSelection.ranges[i]; + if (!range) + continue; + this.cm6.dispatch({ selection: import_state.EditorSelection.create([range]) }); + command(); + this.virtualSelection.ranges[i] = this.cm6.state.selection.ranges[0]; + } + this.cm6.dispatch({ selection: this.virtualSelection }); + this.virtualSelection = null; + } +}; +CodeMirror.isMac = typeof navigator != "undefined" && /* @__PURE__ */ /Mac/.test(navigator.platform); +CodeMirror.Pos = Pos; +CodeMirror.StringStream = import_language.StringStream; +CodeMirror.commands = { + cursorCharLeft: function(cm) { + (0, import_commands.cursorCharLeft)(cm.cm6); + }, + redo: function(cm) { + runHistoryCommand(cm, false); + }, + undo: function(cm) { + runHistoryCommand(cm, true); + }, + newlineAndIndent: function(cm) { + (0, import_commands.insertNewlineAndIndent)({ + state: cm.cm6.state, + dispatch: (tr) => { + return dispatchChange(cm, tr); + } + }); + }, + indentAuto: function(cm) { + (0, import_commands.indentSelection)(cm.cm6); + } +}; +CodeMirror.defineOption = function(name, val, setter) { +}; +CodeMirror.isWordChar = function(ch) { + return wordChar.test(ch); +}; +CodeMirror.keys = keys; +CodeMirror.keyMap = {}; +CodeMirror.addClass = function() { +}; +CodeMirror.rmClass = function() { +}; +CodeMirror.e_preventDefault = function(e) { + e.preventDefault(); +}; +CodeMirror.e_stop = function(e) { + var _a, _b; + (_a = e === null || e === void 0 ? void 0 : e.stopPropagation) === null || _a === void 0 ? void 0 : _a.call(e); + (_b = e === null || e === void 0 ? void 0 : e.preventDefault) === null || _b === void 0 ? void 0 : _b.call(e); +}; +CodeMirror.keyName = function(e) { + var key = e.key; + if (ignoredKeys[key]) + return; + if (key == "Escape") + key = "Esc"; + if (key == " ") + key = "Space"; + if (key.length > 1) { + key = key.replace(/Numpad|Arrow/, ""); + } + if (key.length == 1) + key = key.toUpperCase(); + var name = ""; + if (e.ctrlKey) { + name += "Ctrl-"; + } + if (e.altKey) { + name += "Alt-"; + } + if ((name || key.length > 1) && e.shiftKey) { + name += "Shift-"; + } + name += key; + return name; +}; +CodeMirror.vimKey = function vimKey(e) { + var key = e.key; + if (ignoredKeys[key]) + return; + if (key.length > 1 && key[0] == "n") { + key = key.replace("Numpad", ""); + } + key = specialKey[key] || key; + var name = ""; + if (e.ctrlKey) { + name += "C-"; + } + if (e.altKey) { + name += "A-"; + } + if (e.metaKey) { + name += "M-"; + } + if (CodeMirror.isMac && e.altKey && !e.metaKey && !e.ctrlKey) { + name = name.slice(2); + } + if ((name || key.length > 1) && e.shiftKey) { + name += "S-"; + } + name += key; + if (name.length > 1) { + name = "<" + name + ">"; + } + return name; +}; +CodeMirror.lookupKey = function lookupKey(key, map, handle) { + var result = CodeMirror.keys[key]; + if (result) + handle(result); +}; +CodeMirror.on = on; +CodeMirror.off = off; +CodeMirror.signal = signal; +CodeMirror.findMatchingTag = findMatchingTag; +CodeMirror.findEnclosingTag = findEnclosingTag; +function dialogDiv(cm, template, bottom) { + var dialog = document.createElement("div"); + dialog.appendChild(template); + return dialog; +} +function closeNotification(cm, newVal) { + if (cm.state.currentNotificationClose) + cm.state.currentNotificationClose(); + cm.state.currentNotificationClose = newVal; +} +function openNotification(cm, template, options) { + closeNotification(cm, close); + var dialog = dialogDiv(cm, template, options && options.bottom); + var closed = false; + var doneTimer; + var duration = options && typeof options.duration !== "undefined" ? options.duration : 5e3; + function close() { + if (closed) + return; + closed = true; + clearTimeout(doneTimer); + dialog.remove(); + hideDialog(cm, dialog); + } + dialog.onclick = function(e) { + e.preventDefault(); + close(); + }; + showDialog(cm, dialog); + if (duration) + doneTimer = setTimeout(close, duration); + return close; +} +function showDialog(cm, dialog) { + var oldDialog = cm.state.dialog; + cm.state.dialog = dialog; + if (dialog && oldDialog !== dialog) { + if (oldDialog && oldDialog.contains(document.activeElement)) + cm.focus(); + if (oldDialog && oldDialog.parentElement) { + oldDialog.parentElement.replaceChild(dialog, oldDialog); + } else if (oldDialog) { + oldDialog.remove(); + } + CodeMirror.signal(cm, "dialog"); + } +} +function hideDialog(cm, dialog) { + if (cm.state.dialog == dialog) { + cm.state.dialog = null; + CodeMirror.signal(cm, "dialog"); + } +} +function openDialog(me, template, callback, options) { + if (!options) + options = {}; + closeNotification(me, void 0); + var dialog = dialogDiv(me, template, options.bottom); + var closed = false; + showDialog(me, dialog); + function close(newVal) { + if (typeof newVal == "string") { + inp.value = newVal; + } else { + if (closed) + return; + closed = true; + hideDialog(me, dialog); + if (!me.state.dialog) + me.focus(); + if (options.onClose) + options.onClose(dialog); + } + } + var inp = dialog.getElementsByTagName("input")[0]; + if (inp) { + if (options.value) { + inp.value = options.value; + if (options.selectValueOnOpen !== false) + inp.select(); + } + if (options.onInput) + CodeMirror.on(inp, "input", function(e) { + options.onInput(e, inp.value, close); + }); + if (options.onKeyUp) + CodeMirror.on(inp, "keyup", function(e) { + options.onKeyUp(e, inp.value, close); + }); + CodeMirror.on(inp, "keydown", function(e) { + if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { + return; + } + if (e.keyCode == 13) + callback(inp.value); + if (e.keyCode == 27 || options.closeOnEnter !== false && e.keyCode == 13) { + inp.blur(); + CodeMirror.e_stop(e); + close(); + } + }); + if (options.closeOnBlur !== false) + CodeMirror.on(inp, "blur", function() { + setTimeout(function() { + if (document.activeElement === inp) + return; + close(); + }); + }); + inp.focus(); + } + return close; +} +var matching = { "(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<", "<": ">>", ">": "<<" }; +function bracketRegex(config3) { + return config3 && config3.bracketRegex || /[(){}[\]]/; +} +function scanForBracket(cm, where, dir, style, config3) { + var maxScanLen = config3 && config3.maxScanLineLength || 1e4; + var maxScanLines = config3 && config3.maxScanLines || 1e3; + var stack = []; + var re = bracketRegex(config3); + var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1) : Math.max(cm.firstLine() - 1, where.line - maxScanLines); + for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) { + var line = cm.getLine(lineNo); + if (!line) + continue; + var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1; + if (line.length > maxScanLen) + continue; + if (lineNo == where.line) + pos = where.ch - (dir < 0 ? 1 : 0); + for (; pos != end; pos += dir) { + var ch = line.charAt(pos); + if (re.test(ch)) { + var match = matching[ch]; + if (match && match.charAt(1) == ">" == dir > 0) + stack.push(ch); + else if (!stack.length) + return { pos: new Pos(lineNo, pos), ch }; + else + stack.pop(); + } + } + } + return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null; +} +function findMatchingTag(cm, pos) { +} +function findEnclosingTag(cm, pos) { + var _a, _b; + var state = cm.cm6.state; + var offset = cm.indexFromPos(pos); + if (offset < state.doc.length) { + var text = state.sliceDoc(offset, offset + 1); + if (text == "<") + offset++; + } + var tree = (0, import_language.ensureSyntaxTree)(state, offset); + var node = (tree === null || tree === void 0 ? void 0 : tree.resolve(offset)) || null; + while (node) { + if (((_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.type.name) == "OpenTag" && ((_b = node.lastChild) === null || _b === void 0 ? void 0 : _b.type.name) == "CloseTag") { + return { + open: convertRange(state.doc, node.firstChild), + close: convertRange(state.doc, node.lastChild) + }; + } + node = node.parent; + } +} +function convertRange(doc, cm6Range) { + return { + from: posFromIndex(doc, cm6Range.from), + to: posFromIndex(doc, cm6Range.to) + }; +} +var Marker = class { + constructor(cm, offset, assoc) { + this.cm = cm; + this.id = cm.$mid++; + this.offset = offset; + this.assoc = assoc; + cm.marks[this.id] = this; + } + clear() { + delete this.cm.marks[this.id]; + } + find() { + if (this.offset == null) + return null; + return this.cm.posFromIndex(this.offset); + } + update(change) { + if (this.offset != null) + this.offset = change.mapPos(this.offset, this.assoc, import_state.MapMode.TrackDel); + } +}; +var Piece = class { + constructor(left, top, height, fontFamily, fontSize, fontWeight, color, className, letter, partial) { + this.left = left; + this.top = top; + this.height = height; + this.fontFamily = fontFamily; + this.fontSize = fontSize; + this.fontWeight = fontWeight; + this.color = color; + this.className = className; + this.letter = letter; + this.partial = partial; + } + draw() { + let elt = document.createElement("div"); + elt.className = this.className; + this.adjust(elt); + return elt; + } + adjust(elt) { + elt.style.left = this.left + "px"; + elt.style.top = this.top + "px"; + elt.style.height = this.height + "px"; + elt.style.lineHeight = this.height + "px"; + elt.style.fontFamily = this.fontFamily; + elt.style.fontSize = this.fontSize; + elt.style.fontWeight = this.fontWeight; + elt.style.color = this.partial ? "transparent" : this.color; + elt.className = this.className; + elt.textContent = this.letter; + } + eq(p) { + return this.left == p.left && this.top == p.top && this.height == p.height && this.fontFamily == p.fontFamily && this.fontSize == p.fontSize && this.fontWeight == p.fontWeight && this.color == p.color && this.className == p.className && this.letter == p.letter; + } +}; +var BlockCursorPlugin = class { + constructor(view, cm) { + this.view = view; + this.rangePieces = []; + this.cursors = []; + this.cm = cm; + this.measureReq = { read: this.readPos.bind(this), write: this.drawSel.bind(this) }; + this.cursorLayer = view.scrollDOM.appendChild(document.createElement("div")); + this.cursorLayer.className = "cm-cursorLayer cm-vimCursorLayer"; + this.cursorLayer.setAttribute("aria-hidden", "true"); + view.requestMeasure(this.measureReq); + this.setBlinkRate(); + } + setBlinkRate() { + this.cursorLayer.style.animationDuration = 1200 + "ms"; + } + update(update) { + if (update.selectionSet || update.geometryChanged || update.viewportChanged) { + this.view.requestMeasure(this.measureReq); + this.cursorLayer.style.animationName = this.cursorLayer.style.animationName == "cm-blink" ? "cm-blink2" : "cm-blink"; + } + } + scheduleRedraw() { + this.view.requestMeasure(this.measureReq); + } + readPos() { + let { state } = this.view; + let cursors = []; + for (let r of state.selection.ranges) { + let prim = r == state.selection.main; + let piece = measureCursor(this.cm, this.view, r, prim); + if (piece) + cursors.push(piece); + } + return { cursors }; + } + drawSel({ cursors }) { + if (cursors.length != this.cursors.length || cursors.some((c, i) => !c.eq(this.cursors[i]))) { + let oldCursors = this.cursorLayer.children; + if (oldCursors.length !== cursors.length) { + this.cursorLayer.textContent = ""; + for (const c of cursors) + this.cursorLayer.appendChild(c.draw()); + } else { + cursors.forEach((c, idx) => c.adjust(oldCursors[idx])); + } + this.cursors = cursors; + } + } + destroy() { + this.cursorLayer.remove(); + } +}; +var themeSpec = { + ".cm-vimMode .cm-line": { + "& ::selection": { backgroundColor: "transparent !important" }, + "&::selection": { backgroundColor: "transparent !important" }, + caretColor: "transparent !important" + }, + ".cm-fat-cursor": { + position: "absolute", + background: "#ff9696", + border: "none", + whiteSpace: "pre" + }, + "&:not(.cm-focused) .cm-fat-cursor": { + background: "none", + outline: "solid 1px #ff9696", + color: "transparent !important" + } +}; +var hideNativeSelection = /* @__PURE__ */ import_state.Prec.highest(/* @__PURE__ */ import_view.EditorView.theme(themeSpec)); +function getBase(view) { + let rect = view.scrollDOM.getBoundingClientRect(); + let left = view.textDirection == import_view.Direction.LTR ? rect.left : rect.right - view.scrollDOM.clientWidth; + return { left: left - view.scrollDOM.scrollLeft, top: rect.top - view.scrollDOM.scrollTop }; +} +function measureCursor(cm, view, cursor, primary) { + let head = cursor.head; + let fatCursor = false; + let hCoeff = 1; + let vim2 = cm.state.vim; + if (vim2 && (!vim2.insertMode || cm.state.overwrite)) { + fatCursor = true; + if (vim2.visualBlock && !primary) + return null; + if (cursor.anchor < cursor.head) + head--; + if (cm.state.overwrite) + hCoeff = 0.2; + else if (vim2.status) + hCoeff = 0.5; + } + if (fatCursor) { + let letter = head < view.state.doc.length && view.state.sliceDoc(head, head + 1); + if (letter && (/[\uDC00-\uDFFF]/.test(letter) && head > 1)) { + head--; + letter = view.state.sliceDoc(head, head + 1); + } + let pos = view.coordsAtPos(head, 1); + if (!pos) + return null; + let base = getBase(view); + let domAtPos = view.domAtPos(head); + let node = domAtPos ? domAtPos.node : view.contentDOM; + while (domAtPos && domAtPos.node instanceof HTMLElement) { + node = domAtPos.node; + domAtPos = { node: domAtPos.node.childNodes[domAtPos.offset], offset: 0 }; + } + if (!(node instanceof HTMLElement)) { + if (!node.parentNode) + return null; + node = node.parentNode; + } + let style = getComputedStyle(node); + let left = pos.left; + if (!letter || letter == "\n" || letter == "\r") { + letter = "\xA0"; + } else if (letter == " ") { + letter = "\xA0"; + var nextPos = view.coordsAtPos(head + 1, -1); + if (nextPos) { + left = nextPos.left - (nextPos.left - pos.left) / parseInt(style.tabSize); + } + } else if (/[\uD800-\uDBFF]/.test(letter) && head < view.state.doc.length - 1) { + letter += view.state.sliceDoc(head + 1, head + 2); + } + let h = pos.bottom - pos.top; + return new Piece(left - base.left, pos.top - base.top + h * (1 - hCoeff), h * hCoeff, style.fontFamily, style.fontSize, style.fontWeight, style.color, primary ? "cm-fat-cursor cm-cursor-primary" : "cm-fat-cursor cm-cursor-secondary", letter, hCoeff != 1); + } else { + return null; + } +} +var FIREFOX_LINUX = typeof navigator != "undefined" && /* @__PURE__ */ /linux/i.test(navigator.platform) && /* @__PURE__ */ / Gecko\/\d+/.exec(navigator.userAgent); +var Vim = /* @__PURE__ */ initVim(CodeMirror); +var HighlightMargin = 250; +var vimStyle = /* @__PURE__ */ import_view.EditorView.baseTheme({ + ".cm-vimMode .cm-cursorLayer:not(.cm-vimCursorLayer)": { + display: "none" + }, + ".cm-vim-panel": { + padding: "0px 10px", + fontFamily: "monospace", + minHeight: "1.3em" + }, + ".cm-vim-panel input": { + border: "none", + outline: "none", + backgroundColor: "inherit" + }, + "&light .cm-searchMatch": { backgroundColor: "#ffff0054" }, + "&dark .cm-searchMatch": { backgroundColor: "#00ffff8a" } +}); +var vimPlugin = /* @__PURE__ */ import_view.ViewPlugin.fromClass(class { + constructor(view) { + this.status = ""; + this.query = null; + this.decorations = import_view.Decoration.none; + this.waitForCopy = false; + this.lastKeydown = ""; + this.useNextTextInput = false; + this.view = view; + const cm = this.cm = new CodeMirror(view); + Vim.enterVimMode(this.cm); + this.view.cm = this.cm; + this.cm.state.vimPlugin = this; + this.blockCursor = new BlockCursorPlugin(view, cm); + this.updateClass(); + this.cm.on("vim-command-done", () => { + if (cm.state.vim) + cm.state.vim.status = ""; + this.blockCursor.scheduleRedraw(); + this.updateStatus(); + }); + this.cm.on("vim-mode-change", (e) => { + cm.state.vim.mode = e.mode; + if (e.subMode) { + cm.state.vim.mode += " block"; + } + cm.state.vim.status = ""; + this.blockCursor.scheduleRedraw(); + this.updateClass(); + this.updateStatus(); + }); + this.cm.on("dialog", () => { + if (this.cm.state.statusbar) { + this.updateStatus(); + } else { + view.dispatch({ + effects: showVimPanel.of(!!this.cm.state.dialog) + }); + } + }); + this.dom = document.createElement("span"); + this.dom.style.cssText = "position: absolute; right: 10px; top: 1px"; + this.statusButton = document.createElement("span"); + this.statusButton.onclick = (e) => { + Vim.handleKey(this.cm, "", "user"); + this.cm.focus(); + }; + this.statusButton.style.cssText = "cursor: pointer"; + } + update(update) { + var _a; + if ((update.viewportChanged || update.docChanged) && this.query) { + this.highlight(this.query); + } + if (update.docChanged) { + this.cm.onChange(update); + } + if (update.selectionSet) { + this.cm.onSelectionChange(); + } + if (update.viewportChanged) + ; + if (this.cm.curOp && !this.cm.curOp.isVimOp) { + this.cm.onBeforeEndOperation(); + } + if (update.transactions) { + for (let tr of update.transactions) + for (let effect of tr.effects) { + if (effect.is(import_search.setSearchQuery)) { + let forVim = (_a = effect.value) === null || _a === void 0 ? void 0 : _a.forVim; + if (!forVim) { + this.highlight(null); + } else { + let query = effect.value.create(); + this.highlight(query); + } + } + } + } + this.blockCursor.update(update); + } + updateClass() { + const state = this.cm.state; + if (!state.vim || state.vim.insertMode && !state.overwrite) + this.view.scrollDOM.classList.remove("cm-vimMode"); + else + this.view.scrollDOM.classList.add("cm-vimMode"); + } + updateStatus() { + let dom = this.cm.state.statusbar; + let vim2 = this.cm.state.vim; + if (!dom || !vim2) + return; + let dialog = this.cm.state.dialog; + if (dialog) { + if (dialog.parentElement != dom) { + dom.textContent = ""; + dom.appendChild(dialog); + } + } else { + dom.textContent = ""; + var status = (vim2.mode || "normal").toUpperCase(); + if (vim2.insertModeReturn) + status += "(C-O)"; + this.statusButton.textContent = `--${status}--`; + dom.appendChild(this.statusButton); + } + this.dom.textContent = vim2.status; + dom.appendChild(this.dom); + } + destroy() { + Vim.leaveVimMode(this.cm); + this.updateClass(); + this.blockCursor.destroy(); + delete this.view.cm; + } + highlight(query) { + this.query = query; + if (!query) + return this.decorations = import_view.Decoration.none; + let { view } = this; + let builder = new import_state.RangeSetBuilder(); + for (let i = 0, ranges = view.visibleRanges, l = ranges.length; i < l; i++) { + let { from, to } = ranges[i]; + while (i < l - 1 && to > ranges[i + 1].from - 2 * HighlightMargin) + to = ranges[++i].to; + query.highlight(view.state, from, to, (from2, to2) => { + builder.add(from2, to2, matchMark); + }); + } + return this.decorations = builder.finish(); + } + handleKey(e, view) { + const key = CodeMirror.vimKey(e); + const cm = this.cm; + if (!key) + return; + let vim2 = cm.state.vim; + if (!vim2) + return; + if (key == "" && !vim2.insertMode && !vim2.visualMode && this.query) { + const searchState = vim2.searchState_; + if (searchState) { + cm.removeOverlay(searchState.getOverlay()); + searchState.setOverlay(null); + } + } + let isCopy = key === "" && !CodeMirror.isMac; + if (isCopy && cm.somethingSelected()) { + this.waitForCopy = true; + return true; + } + vim2.status = (vim2.status || "") + key; + let result = Vim.multiSelectHandleKey(cm, key, "user"); + vim2 = cm.state.vim; + if (!result && vim2.insertMode && cm.state.overwrite) { + if (e.key && e.key.length == 1 && !/\n/.test(e.key)) { + result = true; + cm.overWriteSelection(e.key); + } else if (e.key == "Backspace") { + result = true; + CodeMirror.commands.cursorCharLeft(cm); + } + } + if (result) { + CodeMirror.signal(this.cm, "vim-keypress", key); + e.preventDefault(); + e.stopPropagation(); + this.blockCursor.scheduleRedraw(); + } + this.updateStatus(); + return !!result; + } +}, { + eventHandlers: { + copy: function(e, view) { + if (!this.waitForCopy) + return; + this.waitForCopy = false; + Promise.resolve().then(() => { + var cm = this.cm; + var vim2 = cm.state.vim; + if (!vim2) + return; + if (vim2.insertMode) { + cm.setSelection(cm.getCursor(), cm.getCursor()); + } else { + cm.operation(() => { + if (cm.curOp) + cm.curOp.isVimOp = true; + Vim.handleKey(cm, "", "user"); + }); + } + }); + }, + compositionstart: function(e, view) { + this.useNextTextInput = true; + }, + keypress: function(e, view) { + if (this.lastKeydown == "Dead") + this.handleKey(e, view); + }, + keydown: function(e, view) { + this.lastKeydown = e.key; + if (this.lastKeydown == "Unidentified" || this.lastKeydown == "Process" || this.lastKeydown == "Dead") { + this.useNextTextInput = true; + } else { + this.useNextTextInput = false; + this.handleKey(e, view); + } + } + }, + provide: () => { + return [ + import_view.EditorView.inputHandler.of((view, from, to, text) => { + var _a, _b; + var cm = getCM(view); + if (!cm) + return false; + var vim2 = (_a = cm.state) === null || _a === void 0 ? void 0 : _a.vim; + var vimPlugin2 = cm.state.vimPlugin; + if (vim2 && !vim2.insertMode && !((_b = cm.curOp) === null || _b === void 0 ? void 0 : _b.isVimOp)) { + if (text === "\0\0") { + return true; + } + if (text.length == 1 && vimPlugin2.useNextTextInput) { + vimPlugin2.handleKey({ + key: text, + preventDefault: () => { + }, + stopPropagation: () => { + } + }); + } + forceEndComposition(view); + return true; + } + return false; + }) + ]; + }, + decorations: (v) => v.decorations +}); +function forceEndComposition(view) { + var parent = view.scrollDOM.parentElement; + if (!parent) + return; + if (FIREFOX_LINUX) { + view.contentDOM.textContent = "\0\0"; + view.contentDOM.dispatchEvent(new CustomEvent("compositionend")); + return; + } + var sibling = view.scrollDOM.nextSibling; + var selection = window.getSelection(); + var savedSelection = selection && { + anchorNode: selection.anchorNode, + anchorOffset: selection.anchorOffset, + focusNode: selection.focusNode, + focusOffset: selection.focusOffset + }; + view.scrollDOM.remove(); + parent.insertBefore(view.scrollDOM, sibling); + try { + if (savedSelection && selection) { + selection.setPosition(savedSelection.anchorNode, savedSelection.anchorOffset); + if (savedSelection.focusNode) { + selection.extend(savedSelection.focusNode, savedSelection.focusOffset); + } + } + } catch (e) { + console.error(e); + } + view.focus(); + view.contentDOM.dispatchEvent(new CustomEvent("compositionend")); +} +var matchMark = /* @__PURE__ */ import_view.Decoration.mark({ class: "cm-searchMatch" }); +var showVimPanel = /* @__PURE__ */ import_state.StateEffect.define(); +var vimPanelState = /* @__PURE__ */ import_state.StateField.define({ + create: () => false, + update(value, tr) { + for (let e of tr.effects) + if (e.is(showVimPanel)) + value = e.value; + return value; + }, + provide: (f) => { + return import_view.showPanel.from(f, (on2) => on2 ? createVimPanel : null); + } +}); +function createVimPanel(view) { + let dom = document.createElement("div"); + dom.className = "cm-vim-panel"; + let cm = view.cm; + if (cm.state.dialog) { + dom.appendChild(cm.state.dialog); + } + return { top: false, dom }; +} +function statusPanel(view) { + let dom = document.createElement("div"); + dom.className = "cm-vim-panel"; + let cm = view.cm; + cm.state.statusbar = dom; + cm.state.vimPlugin.updateStatus(); + return { dom }; +} +function vim(options = {}) { + return [ + vimStyle, + vimPlugin, + hideNativeSelection, + options.status ? import_view.showPanel.of(statusPanel) : vimPanelState + ]; +} +function getCM(view) { + return view.cm || null; +} + +// src/CssFile.ts +var CssFile = class { + constructor(name) { + /** File extension. */ + this.extension = "css"; + if (typeof name !== "string" || name.length === 0) { + throw new Error("Invalid file name."); + } + const extensionWithDot = `.${this.extension}`; + const basename = name.endsWith(extensionWithDot) ? name.slice(0, name.length - extensionWithDot.length) : name; + this.name = `${basename}.${this.extension}`; + this.basename = basename; + } +}; + +// src/obsidian/file-system-helpers.ts +var import_obsidian = require("obsidian"); +function getSnippetDirectory(app) { + return `${app.vault.configDir}/snippets/`; +} +async function readSnippetFile(app, file) { + const data = await app.vault.adapter.read( + (0, import_obsidian.normalizePath)(`${getSnippetDirectory(app)}${file.name}`) + ); + return data; +} +async function createSnippetFile(app, fileName, data = "") { + const file = new CssFile(fileName); + await _validateFile(file); + await _createSnippetDirectoryIfNotExists(app); + await app.vault.adapter.write( + (0, import_obsidian.normalizePath)(`${getSnippetDirectory(app)}${file.name}`), + data + ); + return file; +} +async function writeSnippetFile(app, file, data) { + await app.vault.adapter.write( + (0, import_obsidian.normalizePath)(`${getSnippetDirectory(app)}${file.name}`), + data + ); +} +async function checkSnippetExists(app, fileName) { + return app.vault.adapter.exists( + (0, import_obsidian.normalizePath)(`${getSnippetDirectory(app)}${fileName}`) + ); +} +async function deleteSnippetFile(app, file) { + await app.vault.adapter.remove( + (0, import_obsidian.normalizePath)(`${getSnippetDirectory(app)}${file.name}`) + ); +} +function toggleSnippetFileState(app, file) { + var _a; + if (!((_a = app.customCss) == null ? void 0 : _a.enabledSnippets) || !app.customCss.setCssEnabledStatus) { + throw new Error("Failed to enable/disable CSS snippet."); + } + const isEnabled = app.customCss.enabledSnippets.has(file.basename); + app.customCss.setCssEnabledStatus(file.basename, !isEnabled); + return !isEnabled; +} +async function _createSnippetDirectoryIfNotExists(app) { + if (!await app.vault.adapter.exists(getSnippetDirectory(app))) { + await app.vault.adapter.mkdir(getSnippetDirectory(app)); + } +} +async function _validateFile(file) { + const errors = { + exists: "", + regex: "" + }; + if (file.name.length > 0 && await checkSnippetExists(this.app, file.name)) { + errors.exists = "File already exists."; + } + const regex = /^[0-9a-zA-Z\-_ ]+\.css$/; + if (!regex.test(file.name)) { + errors.regex = "Must end with .css and only contain alphanumeric, spaces, dashes, or underscore characters."; + } + if (Object.values(errors).some((x) => x !== "")) { + const message = Object.values(errors).filter((x) => x !== "").reduce((acc, curr) => `${acc} +${curr}`, "Failed to create file."); + throw new Error(message); + } +} + +// src/codemirror-extensions/basic-extensions.ts +var import_commands2 = require("@codemirror/commands"); + +// node_modules/@lezer/css/dist/index.es.js +var import_lr = require("@lezer/lr"); +var import_highlight = require("@lezer/highlight"); +var descendantOp = 94; +var Unit = 1; +var callee = 95; +var identifier = 96; +var VariableName = 2; +var space = [ + 9, + 10, + 11, + 12, + 13, + 32, + 133, + 160, + 5760, + 8192, + 8193, + 8194, + 8195, + 8196, + 8197, + 8198, + 8199, + 8200, + 8201, + 8202, + 8232, + 8233, + 8239, + 8287, + 12288 +]; +var colon = 58; +var parenL = 40; +var underscore = 95; +var bracketL = 91; +var dash = 45; +var period = 46; +var hash = 35; +var percent = 37; +function isAlpha(ch) { + return ch >= 65 && ch <= 90 || ch >= 97 && ch <= 122 || ch >= 161; +} +function isDigit(ch) { + return ch >= 48 && ch <= 57; +} +var identifiers = new import_lr.ExternalTokenizer((input, stack) => { + for (let inside = false, dashes = 0, i = 0; ; i++) { + let { next } = input; + if (isAlpha(next) || next == dash || next == underscore || inside && isDigit(next)) { + if (!inside && (next != dash || i > 0)) + inside = true; + if (dashes === i && next == dash) + dashes++; + input.advance(); + } else { + if (inside) + input.acceptToken(next == parenL ? callee : dashes == 2 && stack.canShift(VariableName) ? VariableName : identifier); + break; + } + } +}); +var descendant = new import_lr.ExternalTokenizer((input) => { + if (space.includes(input.peek(-1))) { + let { next } = input; + if (isAlpha(next) || next == underscore || next == hash || next == period || next == bracketL || next == colon || next == dash) + input.acceptToken(descendantOp); + } +}); +var unitToken = new import_lr.ExternalTokenizer((input) => { + if (!space.includes(input.peek(-1))) { + let { next } = input; + if (next == percent) { + input.advance(); + input.acceptToken(Unit); + } + if (isAlpha(next)) { + do { + input.advance(); + } while (isAlpha(input.next)); + input.acceptToken(Unit); + } + } +}); +var cssHighlighting = (0, import_highlight.styleTags)({ + "AtKeyword import charset namespace keyframes media supports": import_highlight.tags.definitionKeyword, + "from to selector": import_highlight.tags.keyword, + NamespaceName: import_highlight.tags.namespace, + KeyframeName: import_highlight.tags.labelName, + TagName: import_highlight.tags.tagName, + ClassName: import_highlight.tags.className, + PseudoClassName: import_highlight.tags.constant(import_highlight.tags.className), + IdName: import_highlight.tags.labelName, + "FeatureName PropertyName": import_highlight.tags.propertyName, + AttributeName: import_highlight.tags.attributeName, + NumberLiteral: import_highlight.tags.number, + KeywordQuery: import_highlight.tags.keyword, + UnaryQueryOp: import_highlight.tags.operatorKeyword, + "CallTag ValueName": import_highlight.tags.atom, + VariableName: import_highlight.tags.variableName, + Callee: import_highlight.tags.operatorKeyword, + Unit: import_highlight.tags.unit, + "UniversalSelector NestingSelector": import_highlight.tags.definitionOperator, + MatchOp: import_highlight.tags.compareOperator, + "ChildOp SiblingOp, LogicOp": import_highlight.tags.logicOperator, + BinOp: import_highlight.tags.arithmeticOperator, + Important: import_highlight.tags.modifier, + Comment: import_highlight.tags.blockComment, + ParenthesizedContent: import_highlight.tags.special(import_highlight.tags.name), + ColorLiteral: import_highlight.tags.color, + StringLiteral: import_highlight.tags.string, + ":": import_highlight.tags.punctuation, + "PseudoOp #": import_highlight.tags.derefOperator, + "; ,": import_highlight.tags.separator, + "( )": import_highlight.tags.paren, + "[ ]": import_highlight.tags.squareBracket, + "{ }": import_highlight.tags.brace +}); +var spec_callee = { __proto__: null, lang: 32, "nth-child": 32, "nth-last-child": 32, "nth-of-type": 32, "nth-last-of-type": 32, dir: 32, "host-context": 32, url: 60, "url-prefix": 60, domain: 60, regexp: 60, selector: 134 }; +var spec_AtKeyword = { __proto__: null, "@import": 114, "@media": 138, "@charset": 142, "@namespace": 146, "@keyframes": 152, "@supports": 164 }; +var spec_identifier = { __proto__: null, not: 128, only: 128, from: 158, to: 160 }; +var parser = import_lr.LRParser.deserialize({ + version: 14, + states: "7WQYQ[OOO#_Q[OOOOQP'#Cd'#CdOOQP'#Cc'#CcO#fQ[O'#CfO$YQXO'#CaO$aQ[O'#ChO$lQ[O'#DPO$qQ[O'#DTOOQP'#Ed'#EdO$vQdO'#DeO%bQ[O'#DrO$vQdO'#DtO%sQ[O'#DvO&OQ[O'#DyO&TQ[O'#EPO&cQ[O'#EROOQS'#Ec'#EcOOQS'#ET'#ETQYQ[OOO&jQXO'#CdO'_QWO'#DaO'dQWO'#EjO'oQ[O'#EjQOQWOOOOQP'#Cg'#CgOOQP,59Q,59QO#fQ[O,59QO'yQ[O'#EWO(eQWO,58{O(mQ[O,59SO$lQ[O,59kO$qQ[O,59oO'yQ[O,59sO'yQ[O,59uO'yQ[O,59vO(xQ[O'#D`OOQS,58{,58{OOQP'#Ck'#CkOOQO'#C}'#C}OOQP,59S,59SO)PQWO,59SO)UQWO,59SOOQP'#DR'#DROOQP,59k,59kOOQO'#DV'#DVO)ZQ`O,59oOOQS'#Cp'#CpO$vQdO'#CqO)cQvO'#CsO*pQtO,5:POOQO'#Cx'#CxO)UQWO'#CwO+UQWO'#CyOOQS'#Eg'#EgOOQO'#Dh'#DhO+ZQ[O'#DoO+iQWO'#EkO&TQ[O'#DmO+wQWO'#DpOOQO'#El'#ElO(hQWO,5:^O+|QpO,5:`OOQS'#Dx'#DxO,UQWO,5:bO,ZQ[O,5:bOOQO'#D{'#D{O,cQWO,5:eO,hQWO,5:kO,pQWO,5:mOOQS-E8R-E8RO$vQdO,59{O,xQ[O'#EYO-VQWO,5;UO-VQWO,5;UOOQP1G.l1G.lO-|QXO,5:rOOQO-E8U-E8UOOQS1G.g1G.gOOQP1G.n1G.nO)PQWO1G.nO)UQWO1G.nOOQP1G/V1G/VO.ZQ`O1G/ZO.tQXO1G/_O/[QXO1G/aO/rQXO1G/bO0YQWO,59zO0_Q[O'#DOO0fQdO'#CoOOQP1G/Z1G/ZO$vQdO1G/ZO0mQpO,59]OOQS,59_,59_O$vQdO,59aO0uQWO1G/kOOQS,59c,59cO0zQ!bO,59eO1SQWO'#DhO1_QWO,5:TO1dQWO,5:ZO&TQ[O,5:VO&TQ[O'#EZO1lQWO,5;VO1wQWO,5:XO'yQ[O,5:[OOQS1G/x1G/xOOQS1G/z1G/zOOQS1G/|1G/|O2YQWO1G/|O2_QdO'#D|OOQS1G0P1G0POOQS1G0V1G0VOOQS1G0X1G0XO2mQtO1G/gOOQO,5:t,5:tO3TQ[O,5:tOOQO-E8W-E8WO3bQWO1G0pOOQP7+$Y7+$YOOQP7+$u7+$uO$vQdO7+$uOOQS1G/f1G/fO3mQXO'#EiO3tQWO,59jO3yQtO'#EUO4nQdO'#EfO4xQWO,59ZO4}QpO7+$uOOQS1G.w1G.wOOQS1G.{1G.{OOQS7+%V7+%VO5VQWO1G/PO$vQdO1G/oOOQO1G/u1G/uOOQO1G/q1G/qO5[QWO,5:uOOQO-E8X-E8XO5jQXO1G/vOOQS7+%h7+%hO5qQYO'#CsO(hQWO'#E[O5yQdO,5:hOOQS,5:h,5:hO6XQtO'#EXO$vQdO'#EXO7VQdO7+%ROOQO7+%R7+%ROOQO1G0`1G0`O7jQpO<T![;'S%^;'S;=`%o<%lO%^^;TUoWOy%^z!Q%^!Q![;g![;'S%^;'S;=`%o<%lO%^^;nYoW#[UOy%^z!Q%^!Q![;g![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^^[[oW#[UOy%^z!O%^!O!P;g!P!Q%^!Q![>T![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^_?VSpVOy%^z;'S%^;'S;=`%o<%lO%^^?hWjSOy%^z!O%^!O!P;O!P!Q%^!Q![>T![;'S%^;'S;=`%o<%lO%^_@VU#XPOy%^z!Q%^!Q![;g![;'S%^;'S;=`%o<%lO%^~@nTjSOy%^z{@}{;'S%^;'S;=`%o<%lO%^~ASUoWOy@}yzAfz{Bm{;'S@};'S;=`Co<%lO@}~AiTOzAfz{Ax{;'SAf;'S;=`Bg<%lOAf~A{VOzAfz{Ax{!PAf!P!QBb!Q;'SAf;'S;=`Bg<%lOAf~BgOR~~BjP;=`<%lAf~BrWoWOy@}yzAfz{Bm{!P@}!P!QC[!Q;'S@};'S;=`Co<%lO@}~CcSoWR~Oy%^z;'S%^;'S;=`%o<%lO%^~CrP;=`<%l@}^Cz[#[UOy%^z!O%^!O!P;g!P!Q%^!Q![>T![!g%^!g!h<^!h#X%^#X#Y<^#Y;'S%^;'S;=`%o<%lO%^XDuU]POy%^z![%^![!]EX!];'S%^;'S;=`%o<%lO%^XE`S^PoWOy%^z;'S%^;'S;=`%o<%lO%^_EqS!WVOy%^z;'S%^;'S;=`%o<%lO%^YFSSzQOy%^z;'S%^;'S;=`%o<%lO%^XFeU|POy%^z!`%^!`!aFw!a;'S%^;'S;=`%o<%lO%^XGOS|PoWOy%^z;'S%^;'S;=`%o<%lO%^XG_WOy%^z!c%^!c!}Gw!}#T%^#T#oGw#o;'S%^;'S;=`%o<%lO%^XHO[!YPoWOy%^z}%^}!OGw!O!Q%^!Q![Gw![!c%^!c!}Gw!}#T%^#T#oGw#o;'S%^;'S;=`%o<%lO%^XHySxPOy%^z;'S%^;'S;=`%o<%lO%^^I[SvUOy%^z;'S%^;'S;=`%o<%lO%^XIkUOy%^z#b%^#b#cI}#c;'S%^;'S;=`%o<%lO%^XJSUoWOy%^z#W%^#W#XJf#X;'S%^;'S;=`%o<%lO%^XJmS!`PoWOy%^z;'S%^;'S;=`%o<%lO%^XJ|UOy%^z#f%^#f#gJf#g;'S%^;'S;=`%o<%lO%^XKeS!RPOy%^z;'S%^;'S;=`%o<%lO%^_KvS!QVOy%^z;'S%^;'S;=`%o<%lO%^ZLXU!PPOy%^z!_%^!_!`6y!`;'S%^;'S;=`%o<%lO%^WLnP;=`<%l$}", + tokenizers: [descendant, unitToken, identifiers, 0, 1, 2, 3], + topRules: { "StyleSheet": [0, 4], "Styles": [1, 84] }, + specialized: [{ term: 95, get: (value) => spec_callee[value] || -1 }, { term: 56, get: (value) => spec_AtKeyword[value] || -1 }, { term: 96, get: (value) => spec_identifier[value] || -1 }], + tokenPrec: 1123 +}); + +// node_modules/@codemirror/lang-css/dist/index.js +var import_language2 = require("@codemirror/language"); +var import_common = require("@lezer/common"); +var _properties = null; +function properties() { + if (!_properties && typeof document == "object" && document.body) { + let { style } = document.body, names = [], seen = /* @__PURE__ */ new Set(); + for (let prop in style) + if (prop != "cssText" && prop != "cssFloat") { + if (typeof style[prop] == "string") { + if (/[A-Z]/.test(prop)) + prop = prop.replace(/[A-Z]/g, (ch) => "-" + ch.toLowerCase()); + if (!seen.has(prop)) { + names.push(prop); + seen.add(prop); + } + } + } + _properties = names.sort().map((name) => ({ type: "property", label: name })); + } + return _properties || []; +} +var pseudoClasses = /* @__PURE__ */ [ + "active", + "after", + "any-link", + "autofill", + "backdrop", + "before", + "checked", + "cue", + "default", + "defined", + "disabled", + "empty", + "enabled", + "file-selector-button", + "first", + "first-child", + "first-letter", + "first-line", + "first-of-type", + "focus", + "focus-visible", + "focus-within", + "fullscreen", + "has", + "host", + "host-context", + "hover", + "in-range", + "indeterminate", + "invalid", + "is", + "lang", + "last-child", + "last-of-type", + "left", + "link", + "marker", + "modal", + "not", + "nth-child", + "nth-last-child", + "nth-last-of-type", + "nth-of-type", + "only-child", + "only-of-type", + "optional", + "out-of-range", + "part", + "placeholder", + "placeholder-shown", + "read-only", + "read-write", + "required", + "right", + "root", + "scope", + "selection", + "slotted", + "target", + "target-text", + "valid", + "visited", + "where" +].map((name) => ({ type: "class", label: name })); +var values = /* @__PURE__ */ [ + "above", + "absolute", + "activeborder", + "additive", + "activecaption", + "after-white-space", + "ahead", + "alias", + "all", + "all-scroll", + "alphabetic", + "alternate", + "always", + "antialiased", + "appworkspace", + "asterisks", + "attr", + "auto", + "auto-flow", + "avoid", + "avoid-column", + "avoid-page", + "avoid-region", + "axis-pan", + "background", + "backwards", + "baseline", + "below", + "bidi-override", + "blink", + "block", + "block-axis", + "bold", + "bolder", + "border", + "border-box", + "both", + "bottom", + "break", + "break-all", + "break-word", + "bullets", + "button", + "button-bevel", + "buttonface", + "buttonhighlight", + "buttonshadow", + "buttontext", + "calc", + "capitalize", + "caps-lock-indicator", + "caption", + "captiontext", + "caret", + "cell", + "center", + "checkbox", + "circle", + "cjk-decimal", + "clear", + "clip", + "close-quote", + "col-resize", + "collapse", + "color", + "color-burn", + "color-dodge", + "column", + "column-reverse", + "compact", + "condensed", + "contain", + "content", + "contents", + "content-box", + "context-menu", + "continuous", + "copy", + "counter", + "counters", + "cover", + "crop", + "cross", + "crosshair", + "currentcolor", + "cursive", + "cyclic", + "darken", + "dashed", + "decimal", + "decimal-leading-zero", + "default", + "default-button", + "dense", + "destination-atop", + "destination-in", + "destination-out", + "destination-over", + "difference", + "disc", + "discard", + "disclosure-closed", + "disclosure-open", + "document", + "dot-dash", + "dot-dot-dash", + "dotted", + "double", + "down", + "e-resize", + "ease", + "ease-in", + "ease-in-out", + "ease-out", + "element", + "ellipse", + "ellipsis", + "embed", + "end", + "ethiopic-abegede-gez", + "ethiopic-halehame-aa-er", + "ethiopic-halehame-gez", + "ew-resize", + "exclusion", + "expanded", + "extends", + "extra-condensed", + "extra-expanded", + "fantasy", + "fast", + "fill", + "fill-box", + "fixed", + "flat", + "flex", + "flex-end", + "flex-start", + "footnotes", + "forwards", + "from", + "geometricPrecision", + "graytext", + "grid", + "groove", + "hand", + "hard-light", + "help", + "hidden", + "hide", + "higher", + "highlight", + "highlighttext", + "horizontal", + "hsl", + "hsla", + "hue", + "icon", + "ignore", + "inactiveborder", + "inactivecaption", + "inactivecaptiontext", + "infinite", + "infobackground", + "infotext", + "inherit", + "initial", + "inline", + "inline-axis", + "inline-block", + "inline-flex", + "inline-grid", + "inline-table", + "inset", + "inside", + "intrinsic", + "invert", + "italic", + "justify", + "keep-all", + "landscape", + "large", + "larger", + "left", + "level", + "lighter", + "lighten", + "line-through", + "linear", + "linear-gradient", + "lines", + "list-item", + "listbox", + "listitem", + "local", + "logical", + "loud", + "lower", + "lower-hexadecimal", + "lower-latin", + "lower-norwegian", + "lowercase", + "ltr", + "luminosity", + "manipulation", + "match", + "matrix", + "matrix3d", + "medium", + "menu", + "menutext", + "message-box", + "middle", + "min-intrinsic", + "mix", + "monospace", + "move", + "multiple", + "multiple_mask_images", + "multiply", + "n-resize", + "narrower", + "ne-resize", + "nesw-resize", + "no-close-quote", + "no-drop", + "no-open-quote", + "no-repeat", + "none", + "normal", + "not-allowed", + "nowrap", + "ns-resize", + "numbers", + "numeric", + "nw-resize", + "nwse-resize", + "oblique", + "opacity", + "open-quote", + "optimizeLegibility", + "optimizeSpeed", + "outset", + "outside", + "outside-shape", + "overlay", + "overline", + "padding", + "padding-box", + "painted", + "page", + "paused", + "perspective", + "pinch-zoom", + "plus-darker", + "plus-lighter", + "pointer", + "polygon", + "portrait", + "pre", + "pre-line", + "pre-wrap", + "preserve-3d", + "progress", + "push-button", + "radial-gradient", + "radio", + "read-only", + "read-write", + "read-write-plaintext-only", + "rectangle", + "region", + "relative", + "repeat", + "repeating-linear-gradient", + "repeating-radial-gradient", + "repeat-x", + "repeat-y", + "reset", + "reverse", + "rgb", + "rgba", + "ridge", + "right", + "rotate", + "rotate3d", + "rotateX", + "rotateY", + "rotateZ", + "round", + "row", + "row-resize", + "row-reverse", + "rtl", + "run-in", + "running", + "s-resize", + "sans-serif", + "saturation", + "scale", + "scale3d", + "scaleX", + "scaleY", + "scaleZ", + "screen", + "scroll", + "scrollbar", + "scroll-position", + "se-resize", + "self-start", + "self-end", + "semi-condensed", + "semi-expanded", + "separate", + "serif", + "show", + "single", + "skew", + "skewX", + "skewY", + "skip-white-space", + "slide", + "slider-horizontal", + "slider-vertical", + "sliderthumb-horizontal", + "sliderthumb-vertical", + "slow", + "small", + "small-caps", + "small-caption", + "smaller", + "soft-light", + "solid", + "source-atop", + "source-in", + "source-out", + "source-over", + "space", + "space-around", + "space-between", + "space-evenly", + "spell-out", + "square", + "start", + "static", + "status-bar", + "stretch", + "stroke", + "stroke-box", + "sub", + "subpixel-antialiased", + "svg_masks", + "super", + "sw-resize", + "symbolic", + "symbols", + "system-ui", + "table", + "table-caption", + "table-cell", + "table-column", + "table-column-group", + "table-footer-group", + "table-header-group", + "table-row", + "table-row-group", + "text", + "text-bottom", + "text-top", + "textarea", + "textfield", + "thick", + "thin", + "threeddarkshadow", + "threedface", + "threedhighlight", + "threedlightshadow", + "threedshadow", + "to", + "top", + "transform", + "translate", + "translate3d", + "translateX", + "translateY", + "translateZ", + "transparent", + "ultra-condensed", + "ultra-expanded", + "underline", + "unidirectional-pan", + "unset", + "up", + "upper-latin", + "uppercase", + "url", + "var", + "vertical", + "vertical-text", + "view-box", + "visible", + "visibleFill", + "visiblePainted", + "visibleStroke", + "visual", + "w-resize", + "wait", + "wave", + "wider", + "window", + "windowframe", + "windowtext", + "words", + "wrap", + "wrap-reverse", + "x-large", + "x-small", + "xor", + "xx-large", + "xx-small" +].map((name) => ({ type: "keyword", label: name })).concat(/* @__PURE__ */ [ + "aliceblue", + "antiquewhite", + "aqua", + "aquamarine", + "azure", + "beige", + "bisque", + "black", + "blanchedalmond", + "blue", + "blueviolet", + "brown", + "burlywood", + "cadetblue", + "chartreuse", + "chocolate", + "coral", + "cornflowerblue", + "cornsilk", + "crimson", + "cyan", + "darkblue", + "darkcyan", + "darkgoldenrod", + "darkgray", + "darkgreen", + "darkkhaki", + "darkmagenta", + "darkolivegreen", + "darkorange", + "darkorchid", + "darkred", + "darksalmon", + "darkseagreen", + "darkslateblue", + "darkslategray", + "darkturquoise", + "darkviolet", + "deeppink", + "deepskyblue", + "dimgray", + "dodgerblue", + "firebrick", + "floralwhite", + "forestgreen", + "fuchsia", + "gainsboro", + "ghostwhite", + "gold", + "goldenrod", + "gray", + "grey", + "green", + "greenyellow", + "honeydew", + "hotpink", + "indianred", + "indigo", + "ivory", + "khaki", + "lavender", + "lavenderblush", + "lawngreen", + "lemonchiffon", + "lightblue", + "lightcoral", + "lightcyan", + "lightgoldenrodyellow", + "lightgray", + "lightgreen", + "lightpink", + "lightsalmon", + "lightseagreen", + "lightskyblue", + "lightslategray", + "lightsteelblue", + "lightyellow", + "lime", + "limegreen", + "linen", + "magenta", + "maroon", + "mediumaquamarine", + "mediumblue", + "mediumorchid", + "mediumpurple", + "mediumseagreen", + "mediumslateblue", + "mediumspringgreen", + "mediumturquoise", + "mediumvioletred", + "midnightblue", + "mintcream", + "mistyrose", + "moccasin", + "navajowhite", + "navy", + "oldlace", + "olive", + "olivedrab", + "orange", + "orangered", + "orchid", + "palegoldenrod", + "palegreen", + "paleturquoise", + "palevioletred", + "papayawhip", + "peachpuff", + "peru", + "pink", + "plum", + "powderblue", + "purple", + "rebeccapurple", + "red", + "rosybrown", + "royalblue", + "saddlebrown", + "salmon", + "sandybrown", + "seagreen", + "seashell", + "sienna", + "silver", + "skyblue", + "slateblue", + "slategray", + "snow", + "springgreen", + "steelblue", + "tan", + "teal", + "thistle", + "tomato", + "turquoise", + "violet", + "wheat", + "white", + "whitesmoke", + "yellow", + "yellowgreen" +].map((name) => ({ type: "constant", label: name }))); +var tags2 = /* @__PURE__ */ [ + "a", + "abbr", + "address", + "article", + "aside", + "b", + "bdi", + "bdo", + "blockquote", + "body", + "br", + "button", + "canvas", + "caption", + "cite", + "code", + "col", + "colgroup", + "dd", + "del", + "details", + "dfn", + "dialog", + "div", + "dl", + "dt", + "em", + "figcaption", + "figure", + "footer", + "form", + "header", + "hgroup", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "hr", + "html", + "i", + "iframe", + "img", + "input", + "ins", + "kbd", + "label", + "legend", + "li", + "main", + "meter", + "nav", + "ol", + "output", + "p", + "pre", + "ruby", + "section", + "select", + "small", + "source", + "span", + "strong", + "sub", + "summary", + "sup", + "table", + "tbody", + "td", + "template", + "textarea", + "tfoot", + "th", + "thead", + "tr", + "u", + "ul" +].map((name) => ({ type: "type", label: name })); +var identifier2 = /^(\w[\w-]*|-\w[\w-]*|)$/; +var variable = /^-(-[\w-]*)?$/; +function isVarArg(node, doc) { + var _a; + if (node.name == "(" || node.type.isError) + node = node.parent || node; + if (node.name != "ArgList") + return false; + let callee2 = (_a = node.parent) === null || _a === void 0 ? void 0 : _a.firstChild; + if ((callee2 === null || callee2 === void 0 ? void 0 : callee2.name) != "Callee") + return false; + return doc.sliceString(callee2.from, callee2.to) == "var"; +} +var VariablesByNode = /* @__PURE__ */ new import_common.NodeWeakMap(); +var declSelector = ["Declaration"]; +function astTop(node) { + for (let cur2 = node; ; ) { + if (cur2.type.isTop) + return cur2; + if (!(cur2 = cur2.parent)) + return node; + } +} +function variableNames(doc, node, isVariable) { + if (node.to - node.from > 4096) { + let known = VariablesByNode.get(node); + if (known) + return known; + let result = [], seen = /* @__PURE__ */ new Set(), cursor = node.cursor(import_common.IterMode.IncludeAnonymous); + if (cursor.firstChild()) + do { + for (let option of variableNames(doc, cursor.node, isVariable)) + if (!seen.has(option.label)) { + seen.add(option.label); + result.push(option); + } + } while (cursor.nextSibling()); + VariablesByNode.set(node, result); + return result; + } else { + let result = [], seen = /* @__PURE__ */ new Set(); + node.cursor().iterate((node2) => { + var _a; + if (isVariable(node2) && node2.matchContext(declSelector) && ((_a = node2.node.nextSibling) === null || _a === void 0 ? void 0 : _a.name) == ":") { + let name = doc.sliceString(node2.from, node2.to); + if (!seen.has(name)) { + seen.add(name); + result.push({ label: name, type: "variable" }); + } + } + }); + return result; + } +} +var defineCSSCompletionSource = (isVariable) => (context) => { + let { state, pos } = context, node = (0, import_language2.syntaxTree)(state).resolveInner(pos, -1); + let isDash = node.type.isError && node.from == node.to - 1 && state.doc.sliceString(node.from, node.to) == "-"; + if (node.name == "PropertyName" || (isDash || node.name == "TagName") && /^(Block|Styles)$/.test(node.resolve(node.to).name)) + return { from: node.from, options: properties(), validFor: identifier2 }; + if (node.name == "ValueName") + return { from: node.from, options: values, validFor: identifier2 }; + if (node.name == "PseudoClassName") + return { from: node.from, options: pseudoClasses, validFor: identifier2 }; + if (isVariable(node) || (context.explicit || isDash) && isVarArg(node, state.doc)) + return { + from: isVariable(node) || isDash ? node.from : pos, + options: variableNames(state.doc, astTop(node), isVariable), + validFor: variable + }; + if (node.name == "TagName") { + for (let { parent } = node; parent; parent = parent.parent) + if (parent.name == "Block") + return { from: node.from, options: properties(), validFor: identifier2 }; + return { from: node.from, options: tags2, validFor: identifier2 }; + } + if (!context.explicit) + return null; + let above = node.resolve(pos), before = above.childBefore(pos); + if (before && before.name == ":" && above.name == "PseudoClassSelector") + return { from: pos, options: pseudoClasses, validFor: identifier2 }; + if (before && before.name == ":" && above.name == "Declaration" || above.name == "ArgList") + return { from: pos, options: values, validFor: identifier2 }; + if (above.name == "Block" || above.name == "Styles") + return { from: pos, options: properties(), validFor: identifier2 }; + return null; +}; +var cssCompletionSource = /* @__PURE__ */ defineCSSCompletionSource((n) => n.name == "VariableName"); +var cssLanguage = /* @__PURE__ */ import_language2.LRLanguage.define({ + name: "css", + parser: /* @__PURE__ */ parser.configure({ + props: [ + /* @__PURE__ */ import_language2.indentNodeProp.add({ + Declaration: /* @__PURE__ */ (0, import_language2.continuedIndent)() + }), + /* @__PURE__ */ import_language2.foldNodeProp.add({ + Block: import_language2.foldInside + }) + ] + }), + languageData: { + commentTokens: { block: { open: "/*", close: "*/" } }, + indentOnInput: /^\s*\}$/, + wordChars: "-" + } +}); +function css() { + return new import_language2.LanguageSupport(cssLanguage, cssLanguage.data.of({ autocomplete: cssCompletionSource })); +} + +// src/codemirror-extensions/basic-extensions.ts +var import_language5 = require("@codemirror/language"); +var import_state3 = require("@codemirror/state"); +var import_view4 = require("@codemirror/view"); + +// node_modules/@codemirror/autocomplete/dist/index.js +var import_state2 = require("@codemirror/state"); +var import_view2 = require("@codemirror/view"); +var import_language3 = require("@codemirror/language"); +var CompletionContext = class { + /** + Create a new completion context. (Mostly useful for testing + completion sources—in the editor, the extension will create + these for you.) + */ + constructor(state, pos, explicit) { + this.state = state; + this.pos = pos; + this.explicit = explicit; + this.abortListeners = []; + } + /** + Get the extent, content, and (if there is a token) type of the + token before `this.pos`. + */ + tokenBefore(types) { + let token = (0, import_language3.syntaxTree)(this.state).resolveInner(this.pos, -1); + while (token && types.indexOf(token.name) < 0) + token = token.parent; + return token ? { + from: token.from, + to: this.pos, + text: this.state.sliceDoc(token.from, this.pos), + type: token.type + } : null; + } + /** + Get the match of the given expression directly before the + cursor. + */ + matchBefore(expr) { + let line = this.state.doc.lineAt(this.pos); + let start = Math.max(line.from, this.pos - 250); + let str = line.text.slice(start - line.from, this.pos - line.from); + let found = str.search(ensureAnchor(expr, false)); + return found < 0 ? null : { from: start + found, to: this.pos, text: str.slice(found) }; + } + /** + Yields true when the query has been aborted. Can be useful in + asynchronous queries to avoid doing work that will be ignored. + */ + get aborted() { + return this.abortListeners == null; + } + /** + Allows you to register abort handlers, which will be called when + the query is + [aborted](https://codemirror.net/6/docs/ref/#autocomplete.CompletionContext.aborted). + */ + addEventListener(type, listener) { + if (type == "abort" && this.abortListeners) + this.abortListeners.push(listener); + } +}; +function toSet(chars) { + let flat = Object.keys(chars).join(""); + let words = /\w/.test(flat); + if (words) + flat = flat.replace(/\w/g, ""); + return `[${words ? "\\w" : ""}${flat.replace(/[^\w\s]/g, "\\$&")}]`; +} +function prefixMatch(options) { + let first = /* @__PURE__ */ Object.create(null), rest = /* @__PURE__ */ Object.create(null); + for (let { label } of options) { + first[label[0]] = true; + for (let i = 1; i < label.length; i++) + rest[label[i]] = true; + } + let source = toSet(first) + toSet(rest) + "*$"; + return [new RegExp("^" + source), new RegExp(source)]; +} +function completeFromList(list) { + let options = list.map((o) => typeof o == "string" ? { label: o } : o); + let [validFor, match] = options.every((o) => /^\w+$/.test(o.label)) ? [/\w*$/, /\w+$/] : prefixMatch(options); + return (context) => { + let token = context.matchBefore(match); + return token || context.explicit ? { from: token ? token.from : context.pos, options, validFor } : null; + }; +} +var Option = class { + constructor(completion, source, match, score2) { + this.completion = completion; + this.source = source; + this.match = match; + this.score = score2; + } +}; +function cur(state) { + return state.selection.main.from; +} +function ensureAnchor(expr, start) { + var _a; + let { source } = expr; + let addStart = start && source[0] != "^", addEnd = source[source.length - 1] != "$"; + if (!addStart && !addEnd) + return expr; + return new RegExp(`${addStart ? "^" : ""}(?:${source})${addEnd ? "$" : ""}`, (_a = expr.flags) !== null && _a !== void 0 ? _a : expr.ignoreCase ? "i" : ""); +} +var pickedCompletion = /* @__PURE__ */ import_state2.Annotation.define(); +function insertCompletionText(state, text, from, to) { + let { main } = state.selection, fromOff = from - main.from, toOff = to - main.from; + return Object.assign(Object.assign({}, state.changeByRange((range) => { + if (range != main && from != to && state.sliceDoc(range.from + fromOff, range.from + toOff) != state.sliceDoc(from, to)) + return { range }; + return { + changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: text }, + range: import_state2.EditorSelection.cursor(range.from + fromOff + text.length) + }; + })), { userEvent: "input.complete" }); +} +var SourceCache = /* @__PURE__ */ new WeakMap(); +function asSource(source) { + if (!Array.isArray(source)) + return source; + let known = SourceCache.get(source); + if (!known) + SourceCache.set(source, known = completeFromList(source)); + return known; +} +var startCompletionEffect = /* @__PURE__ */ import_state2.StateEffect.define(); +var closeCompletionEffect = /* @__PURE__ */ import_state2.StateEffect.define(); +var FuzzyMatcher = class { + constructor(pattern) { + this.pattern = pattern; + this.chars = []; + this.folded = []; + this.any = []; + this.precise = []; + this.byWord = []; + for (let p = 0; p < pattern.length; ) { + let char = (0, import_state2.codePointAt)(pattern, p), size = (0, import_state2.codePointSize)(char); + this.chars.push(char); + let part = pattern.slice(p, p + size), upper = part.toUpperCase(); + this.folded.push((0, import_state2.codePointAt)(upper == part ? part.toLowerCase() : upper, 0)); + p += size; + } + this.astral = pattern.length != this.chars.length; + } + // Matches a given word (completion) against the pattern (input). + // Will return null for no match, and otherwise an array that starts + // with the match score, followed by any number of `from, to` pairs + // indicating the matched parts of `word`. + // + // The score is a number that is more negative the worse the match + // is. See `Penalty` above. + match(word) { + if (this.pattern.length == 0) + return [ + -100 + /* NotFull */ + ]; + if (word.length < this.pattern.length) + return null; + let { chars, folded, any, precise, byWord } = this; + if (chars.length == 1) { + let first = (0, import_state2.codePointAt)(word, 0), firstSize = (0, import_state2.codePointSize)(first); + let score2 = firstSize == word.length ? 0 : -100; + if (first == chars[0]) + ; + else if (first == folded[0]) + score2 += -200; + else + return null; + return [score2, 0, firstSize]; + } + let direct = word.indexOf(this.pattern); + if (direct == 0) + return [word.length == this.pattern.length ? 0 : -100, 0, this.pattern.length]; + let len = chars.length, anyTo = 0; + if (direct < 0) { + for (let i = 0, e = Math.min(word.length, 200); i < e && anyTo < len; ) { + let next = (0, import_state2.codePointAt)(word, i); + if (next == chars[anyTo] || next == folded[anyTo]) + any[anyTo++] = i; + i += (0, import_state2.codePointSize)(next); + } + if (anyTo < len) + return null; + } + let preciseTo = 0; + let byWordTo = 0, byWordFolded = false; + let adjacentTo = 0, adjacentStart = -1, adjacentEnd = -1; + let hasLower = /[a-z]/.test(word), wordAdjacent = true; + for (let i = 0, e = Math.min(word.length, 200), prevType = 0; i < e && byWordTo < len; ) { + let next = (0, import_state2.codePointAt)(word, i); + if (direct < 0) { + if (preciseTo < len && next == chars[preciseTo]) + precise[preciseTo++] = i; + if (adjacentTo < len) { + if (next == chars[adjacentTo] || next == folded[adjacentTo]) { + if (adjacentTo == 0) + adjacentStart = i; + adjacentEnd = i + 1; + adjacentTo++; + } else { + adjacentTo = 0; + } + } + } + let ch, type = next < 255 ? next >= 48 && next <= 57 || next >= 97 && next <= 122 ? 2 : next >= 65 && next <= 90 ? 1 : 0 : (ch = (0, import_state2.fromCodePoint)(next)) != ch.toLowerCase() ? 1 : ch != ch.toUpperCase() ? 2 : 0; + if (!i || type == 1 && hasLower || prevType == 0 && type != 0) { + if (chars[byWordTo] == next || folded[byWordTo] == next && (byWordFolded = true)) + byWord[byWordTo++] = i; + else if (byWord.length) + wordAdjacent = false; + } + prevType = type; + i += (0, import_state2.codePointSize)(next); + } + if (byWordTo == len && byWord[0] == 0 && wordAdjacent) + return this.result(-100 + (byWordFolded ? -200 : 0), byWord, word); + if (adjacentTo == len && adjacentStart == 0) + return [-200 - word.length + (adjacentEnd == word.length ? 0 : -100), 0, adjacentEnd]; + if (direct > -1) + return [-700 - word.length, direct, direct + this.pattern.length]; + if (adjacentTo == len) + return [-200 + -700 - word.length, adjacentStart, adjacentEnd]; + if (byWordTo == len) + return this.result(-100 + (byWordFolded ? -200 : 0) + -700 + (wordAdjacent ? 0 : -1100), byWord, word); + return chars.length == 2 ? null : this.result((any[0] ? -700 : 0) + -200 + -1100, any, word); + } + result(score2, positions, word) { + let result = [score2 - word.length], i = 1; + for (let pos of positions) { + let to = pos + (this.astral ? (0, import_state2.codePointSize)((0, import_state2.codePointAt)(word, pos)) : 1); + if (i > 1 && result[i - 1] == pos) + result[i - 1] = to; + else { + result[i++] = pos; + result[i++] = to; + } + } + return result; + } +}; +var completionConfig = /* @__PURE__ */ import_state2.Facet.define({ + combine(configs) { + return (0, import_state2.combineConfig)(configs, { + activateOnTyping: true, + selectOnOpen: true, + override: null, + closeOnBlur: true, + maxRenderedOptions: 100, + defaultKeymap: true, + tooltipClass: () => "", + optionClass: () => "", + aboveCursor: false, + icons: true, + addToOptions: [], + positionInfo: defaultPositionInfo, + compareCompletions: (a, b) => a.label.localeCompare(b.label), + interactionDelay: 75 + }, { + defaultKeymap: (a, b) => a && b, + closeOnBlur: (a, b) => a && b, + icons: (a, b) => a && b, + tooltipClass: (a, b) => (c) => joinClass(a(c), b(c)), + optionClass: (a, b) => (c) => joinClass(a(c), b(c)), + addToOptions: (a, b) => a.concat(b) + }); + } +}); +function joinClass(a, b) { + return a ? b ? a + " " + b : a : b; +} +function defaultPositionInfo(view, list, option, info, space2) { + let rtl = view.textDirection == import_view2.Direction.RTL, left = rtl, narrow = false; + let side = "top", offset, maxWidth; + let spaceLeft = list.left - space2.left, spaceRight = space2.right - list.right; + let infoWidth = info.right - info.left, infoHeight = info.bottom - info.top; + if (left && spaceLeft < Math.min(infoWidth, spaceRight)) + left = false; + else if (!left && spaceRight < Math.min(infoWidth, spaceLeft)) + left = true; + if (infoWidth <= (left ? spaceLeft : spaceRight)) { + offset = Math.max(space2.top, Math.min(option.top, space2.bottom - infoHeight)) - list.top; + maxWidth = Math.min(400, left ? spaceLeft : spaceRight); + } else { + narrow = true; + maxWidth = Math.min( + 400, + (rtl ? list.right : space2.right - list.left) - 30 + /* Margin */ + ); + let spaceBelow = space2.bottom - list.bottom; + if (spaceBelow >= infoHeight || spaceBelow > list.top) { + offset = option.bottom - list.top; + } else { + side = "bottom"; + offset = list.bottom - option.top; + } + } + return { + style: `${side}: ${offset}px; max-width: ${maxWidth}px`, + class: "cm-completionInfo-" + (narrow ? rtl ? "left-narrow" : "right-narrow" : left ? "left" : "right") + }; +} +function optionContent(config3) { + let content = config3.addToOptions.slice(); + if (config3.icons) + content.push({ + render(completion) { + let icon = document.createElement("div"); + icon.classList.add("cm-completionIcon"); + if (completion.type) + icon.classList.add(...completion.type.split(/\s+/g).map((cls) => "cm-completionIcon-" + cls)); + icon.setAttribute("aria-hidden", "true"); + return icon; + }, + position: 20 + }); + content.push({ + render(completion, _s, match) { + let labelElt = document.createElement("span"); + labelElt.className = "cm-completionLabel"; + let { label } = completion, off2 = 0; + for (let j = 1; j < match.length; ) { + let from = match[j++], to = match[j++]; + if (from > off2) + labelElt.appendChild(document.createTextNode(label.slice(off2, from))); + let span = labelElt.appendChild(document.createElement("span")); + span.appendChild(document.createTextNode(label.slice(from, to))); + span.className = "cm-completionMatchedText"; + off2 = to; + } + if (off2 < label.length) + labelElt.appendChild(document.createTextNode(label.slice(off2))); + return labelElt; + }, + position: 50 + }, { + render(completion) { + if (!completion.detail) + return null; + let detailElt = document.createElement("span"); + detailElt.className = "cm-completionDetail"; + detailElt.textContent = completion.detail; + return detailElt; + }, + position: 80 + }); + return content.sort((a, b) => a.position - b.position).map((a) => a.render); +} +function rangeAroundSelected(total, selected, max) { + if (total <= max) + return { from: 0, to: total }; + if (selected < 0) + selected = 0; + if (selected <= total >> 1) { + let off3 = Math.floor(selected / max); + return { from: off3 * max, to: (off3 + 1) * max }; + } + let off2 = Math.floor((total - selected) / max); + return { from: total - (off2 + 1) * max, to: total - off2 * max }; +} +var CompletionTooltip = class { + constructor(view, stateField, applyCompletion2) { + this.view = view; + this.stateField = stateField; + this.applyCompletion = applyCompletion2; + this.info = null; + this.infoDestroy = null; + this.placeInfoReq = { + read: () => this.measureInfo(), + write: (pos) => this.placeInfo(pos), + key: this + }; + this.space = null; + this.currentClass = ""; + let cState = view.state.field(stateField); + let { options, selected } = cState.open; + let config3 = view.state.facet(completionConfig); + this.optionContent = optionContent(config3); + this.optionClass = config3.optionClass; + this.tooltipClass = config3.tooltipClass; + this.range = rangeAroundSelected(options.length, selected, config3.maxRenderedOptions); + this.dom = document.createElement("div"); + this.dom.className = "cm-tooltip-autocomplete"; + this.updateTooltipClass(view.state); + this.dom.addEventListener("mousedown", (e) => { + for (let dom = e.target, match; dom && dom != this.dom; dom = dom.parentNode) { + if (dom.nodeName == "LI" && (match = /-(\d+)$/.exec(dom.id)) && +match[1] < options.length) { + this.applyCompletion(view, options[+match[1]]); + e.preventDefault(); + return; + } + } + }); + this.dom.addEventListener("focusout", (e) => { + let state = view.state.field(this.stateField, false); + if (state && state.tooltip && view.state.facet(completionConfig).closeOnBlur && e.relatedTarget != view.contentDOM) + view.dispatch({ effects: closeCompletionEffect.of(null) }); + }); + this.list = this.dom.appendChild(this.createListBox(options, cState.id, this.range)); + this.list.addEventListener("scroll", () => { + if (this.info) + this.view.requestMeasure(this.placeInfoReq); + }); + } + mount() { + this.updateSel(); + } + update(update) { + var _a, _b, _c; + let cState = update.state.field(this.stateField); + let prevState = update.startState.field(this.stateField); + this.updateTooltipClass(update.state); + if (cState != prevState) { + this.updateSel(); + if (((_a = cState.open) === null || _a === void 0 ? void 0 : _a.disabled) != ((_b = prevState.open) === null || _b === void 0 ? void 0 : _b.disabled)) + this.dom.classList.toggle("cm-tooltip-autocomplete-disabled", !!((_c = cState.open) === null || _c === void 0 ? void 0 : _c.disabled)); + } + } + updateTooltipClass(state) { + let cls = this.tooltipClass(state); + if (cls != this.currentClass) { + for (let c of this.currentClass.split(" ")) + if (c) + this.dom.classList.remove(c); + for (let c of cls.split(" ")) + if (c) + this.dom.classList.add(c); + this.currentClass = cls; + } + } + positioned(space2) { + this.space = space2; + if (this.info) + this.view.requestMeasure(this.placeInfoReq); + } + updateSel() { + let cState = this.view.state.field(this.stateField), open = cState.open; + if (open.selected > -1 && open.selected < this.range.from || open.selected >= this.range.to) { + this.range = rangeAroundSelected(open.options.length, open.selected, this.view.state.facet(completionConfig).maxRenderedOptions); + this.list.remove(); + this.list = this.dom.appendChild(this.createListBox(open.options, cState.id, this.range)); + this.list.addEventListener("scroll", () => { + if (this.info) + this.view.requestMeasure(this.placeInfoReq); + }); + } + if (this.updateSelectedOption(open.selected)) { + this.destroyInfo(); + let { completion } = open.options[open.selected]; + let { info } = completion; + if (!info) + return; + let infoResult = typeof info === "string" ? document.createTextNode(info) : info(completion); + if (!infoResult) + return; + if ("then" in infoResult) { + infoResult.then((obj) => { + if (obj && this.view.state.field(this.stateField, false) == cState) + this.addInfoPane(obj, completion); + }).catch((e) => (0, import_view2.logException)(this.view.state, e, "completion info")); + } else { + this.addInfoPane(infoResult, completion); + } + } + } + addInfoPane(content, completion) { + this.destroyInfo(); + let wrap = this.info = document.createElement("div"); + wrap.className = "cm-tooltip cm-completionInfo"; + if (content.nodeType != null) { + wrap.appendChild(content); + this.infoDestroy = null; + } else { + let { dom, destroy } = content; + wrap.appendChild(dom); + this.infoDestroy = destroy || null; + } + this.dom.appendChild(wrap); + this.view.requestMeasure(this.placeInfoReq); + } + updateSelectedOption(selected) { + let set = null; + for (let opt = this.list.firstChild, i = this.range.from; opt; opt = opt.nextSibling, i++) { + if (opt.nodeName != "LI" || !opt.id) { + i--; + } else if (i == selected) { + if (!opt.hasAttribute("aria-selected")) { + opt.setAttribute("aria-selected", "true"); + set = opt; + } + } else { + if (opt.hasAttribute("aria-selected")) + opt.removeAttribute("aria-selected"); + } + } + if (set) + scrollIntoView(this.list, set); + return set; + } + measureInfo() { + let sel = this.dom.querySelector("[aria-selected]"); + if (!sel || !this.info) + return null; + let listRect = this.dom.getBoundingClientRect(); + let infoRect = this.info.getBoundingClientRect(); + let selRect = sel.getBoundingClientRect(); + let space2 = this.space; + if (!space2) { + let win = this.dom.ownerDocument.defaultView || window; + space2 = { left: 0, top: 0, right: win.innerWidth, bottom: win.innerHeight }; + } + if (selRect.top > Math.min(space2.bottom, listRect.bottom) - 10 || selRect.bottom < Math.max(space2.top, listRect.top) + 10) + return null; + return this.view.state.facet(completionConfig).positionInfo(this.view, listRect, selRect, infoRect, space2); + } + placeInfo(pos) { + if (this.info) { + if (pos) { + if (pos.style) + this.info.style.cssText = pos.style; + this.info.className = "cm-tooltip cm-completionInfo " + (pos.class || ""); + } else { + this.info.style.cssText = "top: -1e6px"; + } + } + } + createListBox(options, id, range) { + const ul = document.createElement("ul"); + ul.id = id; + ul.setAttribute("role", "listbox"); + ul.setAttribute("aria-expanded", "true"); + ul.setAttribute("aria-label", this.view.state.phrase("Completions")); + let curSection = null; + for (let i = range.from; i < range.to; i++) { + let { completion, match } = options[i], { section } = completion; + if (section) { + let name = typeof section == "string" ? section : section.name; + if (name != curSection && (i > range.from || range.from == 0)) { + curSection = name; + if (typeof section != "string" && section.header) { + ul.appendChild(section.header(section)); + } else { + let header = ul.appendChild(document.createElement("completion-section")); + header.textContent = name; + } + } + } + const li = ul.appendChild(document.createElement("li")); + li.id = id + "-" + i; + li.setAttribute("role", "option"); + let cls = this.optionClass(completion); + if (cls) + li.className = cls; + for (let source of this.optionContent) { + let node = source(completion, this.view.state, match); + if (node) + li.appendChild(node); + } + } + if (range.from) + ul.classList.add("cm-completionListIncompleteTop"); + if (range.to < options.length) + ul.classList.add("cm-completionListIncompleteBottom"); + return ul; + } + destroyInfo() { + if (this.info) { + if (this.infoDestroy) + this.infoDestroy(); + this.info.remove(); + this.info = null; + } + } + destroy() { + this.destroyInfo(); + } +}; +function completionTooltip(stateField, applyCompletion2) { + return (view) => new CompletionTooltip(view, stateField, applyCompletion2); +} +function scrollIntoView(container, element) { + let parent = container.getBoundingClientRect(); + let self = element.getBoundingClientRect(); + if (self.top < parent.top) + container.scrollTop -= parent.top - self.top; + else if (self.bottom > parent.bottom) + container.scrollTop += self.bottom - parent.bottom; +} +function score(option) { + return (option.boost || 0) * 100 + (option.apply ? 10 : 0) + (option.info ? 5 : 0) + (option.type ? 1 : 0); +} +function sortOptions(active, state) { + let options = []; + let sections = null; + let addOption = (option) => { + options.push(option); + let { section } = option.completion; + if (section) { + if (!sections) + sections = []; + let name = typeof section == "string" ? section : section.name; + if (!sections.some((s) => s.name == name)) + sections.push(typeof section == "string" ? { name } : section); + } + }; + for (let a of active) + if (a.hasResult()) { + if (a.result.filter === false) { + let getMatch = a.result.getMatch; + for (let option of a.result.options) { + let match = [1e9 - options.length]; + if (getMatch) + for (let n of getMatch(option)) + match.push(n); + addOption(new Option(option, a.source, match, match[0])); + } + } else { + let matcher = new FuzzyMatcher(state.sliceDoc(a.from, a.to)), match; + for (let option of a.result.options) + if (match = matcher.match(option.label)) { + addOption(new Option(option, a.source, match, match[0] + (option.boost || 0))); + } + } + } + if (sections) { + let sectionOrder = /* @__PURE__ */ Object.create(null), pos = 0; + let cmp = (a, b) => { + var _a, _b; + return ((_a = a.rank) !== null && _a !== void 0 ? _a : 1e9) - ((_b = b.rank) !== null && _b !== void 0 ? _b : 1e9) || (a.name < b.name ? -1 : 1); + }; + for (let s of sections.sort(cmp)) { + pos -= 1e5; + sectionOrder[s.name] = pos; + } + for (let option of options) { + let { section } = option.completion; + if (section) + option.score += sectionOrder[typeof section == "string" ? section : section.name]; + } + } + let result = [], prev = null; + let compare = state.facet(completionConfig).compareCompletions; + for (let opt of options.sort((a, b) => b.score - a.score || compare(a.completion, b.completion))) { + let cur2 = opt.completion; + if (!prev || prev.label != cur2.label || prev.detail != cur2.detail || prev.type != null && cur2.type != null && prev.type != cur2.type || prev.apply != cur2.apply || prev.boost != cur2.boost) + result.push(opt); + else if (score(opt.completion) > score(prev)) + result[result.length - 1] = opt; + prev = opt.completion; + } + return result; +} +var CompletionDialog = class { + constructor(options, attrs, tooltip, timestamp, selected, disabled) { + this.options = options; + this.attrs = attrs; + this.tooltip = tooltip; + this.timestamp = timestamp; + this.selected = selected; + this.disabled = disabled; + } + setSelected(selected, id) { + return selected == this.selected || selected >= this.options.length ? this : new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled); + } + static build(active, state, id, prev, conf) { + let options = sortOptions(active, state); + if (!options.length) { + return prev && active.some( + (a) => a.state == 1 + /* Pending */ + ) ? new CompletionDialog(prev.options, prev.attrs, prev.tooltip, prev.timestamp, prev.selected, true) : null; + } + let selected = state.facet(completionConfig).selectOnOpen ? 0 : -1; + if (prev && prev.selected != selected && prev.selected != -1) { + let selectedValue = prev.options[prev.selected].completion; + for (let i = 0; i < options.length; i++) + if (options[i].completion == selectedValue) { + selected = i; + break; + } + } + return new CompletionDialog(options, makeAttrs(id, selected), { + pos: active.reduce((a, b) => b.hasResult() ? Math.min(a, b.from) : a, 1e8), + create: completionTooltip(completionState, applyCompletion), + above: conf.aboveCursor + }, prev ? prev.timestamp : Date.now(), selected, false); + } + map(changes) { + return new CompletionDialog(this.options, this.attrs, Object.assign(Object.assign({}, this.tooltip), { pos: changes.mapPos(this.tooltip.pos) }), this.timestamp, this.selected, this.disabled); + } +}; +var CompletionState = class { + constructor(active, id, open) { + this.active = active; + this.id = id; + this.open = open; + } + static start() { + return new CompletionState(none, "cm-ac-" + Math.floor(Math.random() * 2e6).toString(36), null); + } + update(tr) { + let { state } = tr, conf = state.facet(completionConfig); + let sources = conf.override || state.languageDataAt("autocomplete", cur(state)).map(asSource); + let active = sources.map((source) => { + let value = this.active.find((s) => s.source == source) || new ActiveSource( + source, + this.active.some( + (a) => a.state != 0 + /* Inactive */ + ) ? 1 : 0 + /* Inactive */ + ); + return value.update(tr, conf); + }); + if (active.length == this.active.length && active.every((a, i) => a == this.active[i])) + active = this.active; + let open = this.open; + if (open && tr.docChanged) + open = open.map(tr.changes); + if (tr.selection || active.some((a) => a.hasResult() && tr.changes.touchesRange(a.from, a.to)) || !sameResults(active, this.active)) + open = CompletionDialog.build(active, state, this.id, open, conf); + else if (open && open.disabled && !active.some( + (a) => a.state == 1 + /* Pending */ + )) + open = null; + if (!open && active.every( + (a) => a.state != 1 + /* Pending */ + ) && active.some((a) => a.hasResult())) + active = active.map((a) => a.hasResult() ? new ActiveSource( + a.source, + 0 + /* Inactive */ + ) : a); + for (let effect of tr.effects) + if (effect.is(setSelectedEffect)) + open = open && open.setSelected(effect.value, this.id); + return active == this.active && open == this.open ? this : new CompletionState(active, this.id, open); + } + get tooltip() { + return this.open ? this.open.tooltip : null; + } + get attrs() { + return this.open ? this.open.attrs : baseAttrs; + } +}; +function sameResults(a, b) { + if (a == b) + return true; + for (let iA = 0, iB = 0; ; ) { + while (iA < a.length && !a[iA].hasResult) + iA++; + while (iB < b.length && !b[iB].hasResult) + iB++; + let endA = iA == a.length, endB = iB == b.length; + if (endA || endB) + return endA == endB; + if (a[iA++].result != b[iB++].result) + return false; + } +} +var baseAttrs = { + "aria-autocomplete": "list" +}; +function makeAttrs(id, selected) { + let result = { + "aria-autocomplete": "list", + "aria-haspopup": "listbox", + "aria-controls": id + }; + if (selected > -1) + result["aria-activedescendant"] = id + "-" + selected; + return result; +} +var none = []; +function getUserEvent(tr) { + return tr.isUserEvent("input.type") ? "input" : tr.isUserEvent("delete.backward") ? "delete" : null; +} +var ActiveSource = class { + constructor(source, state, explicitPos = -1) { + this.source = source; + this.state = state; + this.explicitPos = explicitPos; + } + hasResult() { + return false; + } + update(tr, conf) { + let event = getUserEvent(tr), value = this; + if (event) + value = value.handleUserEvent(tr, event, conf); + else if (tr.docChanged) + value = value.handleChange(tr); + else if (tr.selection && value.state != 0) + value = new ActiveSource( + value.source, + 0 + /* Inactive */ + ); + for (let effect of tr.effects) { + if (effect.is(startCompletionEffect)) + value = new ActiveSource(value.source, 1, effect.value ? cur(tr.state) : -1); + else if (effect.is(closeCompletionEffect)) + value = new ActiveSource( + value.source, + 0 + /* Inactive */ + ); + else if (effect.is(setActiveEffect)) { + for (let active of effect.value) + if (active.source == value.source) + value = active; + } + } + return value; + } + handleUserEvent(tr, type, conf) { + return type == "delete" || !conf.activateOnTyping ? this.map(tr.changes) : new ActiveSource( + this.source, + 1 + /* Pending */ + ); + } + handleChange(tr) { + return tr.changes.touchesRange(cur(tr.startState)) ? new ActiveSource( + this.source, + 0 + /* Inactive */ + ) : this.map(tr.changes); + } + map(changes) { + return changes.empty || this.explicitPos < 0 ? this : new ActiveSource(this.source, this.state, changes.mapPos(this.explicitPos)); + } +}; +var ActiveResult = class extends ActiveSource { + constructor(source, explicitPos, result, from, to) { + super(source, 2, explicitPos); + this.result = result; + this.from = from; + this.to = to; + } + hasResult() { + return true; + } + handleUserEvent(tr, type, conf) { + var _a; + let from = tr.changes.mapPos(this.from), to = tr.changes.mapPos(this.to, 1); + let pos = cur(tr.state); + if ((this.explicitPos < 0 ? pos <= from : pos < this.from) || pos > to || type == "delete" && cur(tr.startState) == this.from) + return new ActiveSource( + this.source, + type == "input" && conf.activateOnTyping ? 1 : 0 + /* Inactive */ + ); + let explicitPos = this.explicitPos < 0 ? -1 : tr.changes.mapPos(this.explicitPos), updated; + if (checkValid(this.result.validFor, tr.state, from, to)) + return new ActiveResult(this.source, explicitPos, this.result, from, to); + if (this.result.update && (updated = this.result.update(this.result, from, to, new CompletionContext(tr.state, pos, explicitPos >= 0)))) + return new ActiveResult(this.source, explicitPos, updated, updated.from, (_a = updated.to) !== null && _a !== void 0 ? _a : cur(tr.state)); + return new ActiveSource(this.source, 1, explicitPos); + } + handleChange(tr) { + return tr.changes.touchesRange(this.from, this.to) ? new ActiveSource( + this.source, + 0 + /* Inactive */ + ) : this.map(tr.changes); + } + map(mapping) { + return mapping.empty ? this : new ActiveResult(this.source, this.explicitPos < 0 ? -1 : mapping.mapPos(this.explicitPos), this.result, mapping.mapPos(this.from), mapping.mapPos(this.to, 1)); + } +}; +function checkValid(validFor, state, from, to) { + if (!validFor) + return false; + let text = state.sliceDoc(from, to); + return typeof validFor == "function" ? validFor(text, from, to, state) : ensureAnchor(validFor, true).test(text); +} +var setActiveEffect = /* @__PURE__ */ import_state2.StateEffect.define({ + map(sources, mapping) { + return sources.map((s) => s.map(mapping)); + } +}); +var setSelectedEffect = /* @__PURE__ */ import_state2.StateEffect.define(); +var completionState = /* @__PURE__ */ import_state2.StateField.define({ + create() { + return CompletionState.start(); + }, + update(value, tr) { + return value.update(tr); + }, + provide: (f) => [ + import_view2.showTooltip.from(f, (val) => val.tooltip), + import_view2.EditorView.contentAttributes.from(f, (state) => state.attrs) + ] +}); +function applyCompletion(view, option) { + const apply = option.completion.apply || option.completion.label; + let result = view.state.field(completionState).active.find((a) => a.source == option.source); + if (!(result instanceof ActiveResult)) + return false; + if (typeof apply == "string") + view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) })); + else + apply(view, option.completion, result.from, result.to); + return true; +} +function moveCompletionSelection(forward, by = "option") { + return (view) => { + let cState = view.state.field(completionState, false); + if (!cState || !cState.open || cState.open.disabled || Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay) + return false; + let step = 1, tooltip; + if (by == "page" && (tooltip = (0, import_view2.getTooltip)(view, cState.open.tooltip))) + step = Math.max(2, Math.floor(tooltip.dom.offsetHeight / tooltip.dom.querySelector("li").offsetHeight) - 1); + let { length } = cState.open.options; + let selected = cState.open.selected > -1 ? cState.open.selected + step * (forward ? 1 : -1) : forward ? 0 : length - 1; + if (selected < 0) + selected = by == "page" ? 0 : length - 1; + else if (selected >= length) + selected = by == "page" ? length - 1 : 0; + view.dispatch({ effects: setSelectedEffect.of(selected) }); + return true; + }; +} +var acceptCompletion = (view) => { + let cState = view.state.field(completionState, false); + if (view.state.readOnly || !cState || !cState.open || cState.open.selected < 0 || cState.open.disabled || Date.now() - cState.open.timestamp < view.state.facet(completionConfig).interactionDelay) + return false; + return applyCompletion(view, cState.open.options[cState.open.selected]); +}; +var startCompletion = (view) => { + let cState = view.state.field(completionState, false); + if (!cState) + return false; + view.dispatch({ effects: startCompletionEffect.of(true) }); + return true; +}; +var closeCompletion = (view) => { + let cState = view.state.field(completionState, false); + if (!cState || !cState.active.some( + (a) => a.state != 0 + /* Inactive */ + )) + return false; + view.dispatch({ effects: closeCompletionEffect.of(null) }); + return true; +}; +var RunningQuery = class { + constructor(active, context) { + this.active = active; + this.context = context; + this.time = Date.now(); + this.updates = []; + this.done = void 0; + } +}; +var DebounceTime = 50; +var MaxUpdateCount = 50; +var MinAbortTime = 1e3; +var completionPlugin = /* @__PURE__ */ import_view2.ViewPlugin.fromClass(class { + constructor(view) { + this.view = view; + this.debounceUpdate = -1; + this.running = []; + this.debounceAccept = -1; + this.composing = 0; + for (let active of view.state.field(completionState).active) + if (active.state == 1) + this.startQuery(active); + } + update(update) { + let cState = update.state.field(completionState); + if (!update.selectionSet && !update.docChanged && update.startState.field(completionState) == cState) + return; + let doesReset = update.transactions.some((tr) => { + return (tr.selection || tr.docChanged) && !getUserEvent(tr); + }); + for (let i = 0; i < this.running.length; i++) { + let query = this.running[i]; + if (doesReset || query.updates.length + update.transactions.length > MaxUpdateCount && Date.now() - query.time > MinAbortTime) { + for (let handler of query.context.abortListeners) { + try { + handler(); + } catch (e) { + (0, import_view2.logException)(this.view.state, e); + } + } + query.context.abortListeners = null; + this.running.splice(i--, 1); + } else { + query.updates.push(...update.transactions); + } + } + if (this.debounceUpdate > -1) + clearTimeout(this.debounceUpdate); + this.debounceUpdate = cState.active.some((a) => a.state == 1 && !this.running.some((q) => q.active.source == a.source)) ? setTimeout(() => this.startUpdate(), DebounceTime) : -1; + if (this.composing != 0) + for (let tr of update.transactions) { + if (getUserEvent(tr) == "input") + this.composing = 2; + else if (this.composing == 2 && tr.selection) + this.composing = 3; + } + } + startUpdate() { + this.debounceUpdate = -1; + let { state } = this.view, cState = state.field(completionState); + for (let active of cState.active) { + if (active.state == 1 && !this.running.some((r) => r.active.source == active.source)) + this.startQuery(active); + } + } + startQuery(active) { + let { state } = this.view, pos = cur(state); + let context = new CompletionContext(state, pos, active.explicitPos == pos); + let pending = new RunningQuery(active, context); + this.running.push(pending); + Promise.resolve(active.source(context)).then((result) => { + if (!pending.context.aborted) { + pending.done = result || null; + this.scheduleAccept(); + } + }, (err) => { + this.view.dispatch({ effects: closeCompletionEffect.of(null) }); + (0, import_view2.logException)(this.view.state, err); + }); + } + scheduleAccept() { + if (this.running.every((q) => q.done !== void 0)) + this.accept(); + else if (this.debounceAccept < 0) + this.debounceAccept = setTimeout(() => this.accept(), DebounceTime); + } + // For each finished query in this.running, try to create a result + // or, if appropriate, restart the query. + accept() { + var _a; + if (this.debounceAccept > -1) + clearTimeout(this.debounceAccept); + this.debounceAccept = -1; + let updated = []; + let conf = this.view.state.facet(completionConfig); + for (let i = 0; i < this.running.length; i++) { + let query = this.running[i]; + if (query.done === void 0) + continue; + this.running.splice(i--, 1); + if (query.done) { + let active = new ActiveResult(query.active.source, query.active.explicitPos, query.done, query.done.from, (_a = query.done.to) !== null && _a !== void 0 ? _a : cur(query.updates.length ? query.updates[0].startState : this.view.state)); + for (let tr of query.updates) + active = active.update(tr, conf); + if (active.hasResult()) { + updated.push(active); + continue; + } + } + let current = this.view.state.field(completionState).active.find((a) => a.source == query.active.source); + if (current && current.state == 1) { + if (query.done == null) { + let active = new ActiveSource( + query.active.source, + 0 + /* Inactive */ + ); + for (let tr of query.updates) + active = active.update(tr, conf); + if (active.state != 1) + updated.push(active); + } else { + this.startQuery(current); + } + } + } + if (updated.length) + this.view.dispatch({ effects: setActiveEffect.of(updated) }); + } +}, { + eventHandlers: { + blur(event) { + let state = this.view.state.field(completionState, false); + if (state && state.tooltip && this.view.state.facet(completionConfig).closeOnBlur) { + let dialog = state.open && (0, import_view2.getTooltip)(this.view, state.open.tooltip); + if (!dialog || !dialog.dom.contains(event.relatedTarget)) + this.view.dispatch({ effects: closeCompletionEffect.of(null) }); + } + }, + compositionstart() { + this.composing = 1; + }, + compositionend() { + if (this.composing == 3) { + setTimeout(() => this.view.dispatch({ effects: startCompletionEffect.of(false) }), 20); + } + this.composing = 0; + } + } +}); +var baseTheme = /* @__PURE__ */ import_view2.EditorView.baseTheme({ + ".cm-tooltip.cm-tooltip-autocomplete": { + "& > ul": { + fontFamily: "monospace", + whiteSpace: "nowrap", + overflow: "hidden auto", + maxWidth_fallback: "700px", + maxWidth: "min(700px, 95vw)", + minWidth: "250px", + maxHeight: "10em", + height: "100%", + listStyle: "none", + margin: 0, + padding: 0, + "& > li, & > completion-section": { + padding: "1px 3px", + lineHeight: 1.2 + }, + "& > li": { + overflowX: "hidden", + textOverflow: "ellipsis", + cursor: "pointer" + }, + "& > completion-section": { + display: "list-item", + borderBottom: "1px solid silver", + paddingLeft: "0.5em", + opacity: 0.7 + } + } + }, + "&light .cm-tooltip-autocomplete ul li[aria-selected]": { + background: "#17c", + color: "white" + }, + "&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]": { + background: "#777" + }, + "&dark .cm-tooltip-autocomplete ul li[aria-selected]": { + background: "#347", + color: "white" + }, + "&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]": { + background: "#444" + }, + ".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after": { + content: '"\xB7\xB7\xB7"', + opacity: 0.5, + display: "block", + textAlign: "center" + }, + ".cm-tooltip.cm-completionInfo": { + position: "absolute", + padding: "3px 9px", + width: "max-content", + maxWidth: `${400}px`, + boxSizing: "border-box" + }, + ".cm-completionInfo.cm-completionInfo-left": { right: "100%" }, + ".cm-completionInfo.cm-completionInfo-right": { left: "100%" }, + ".cm-completionInfo.cm-completionInfo-left-narrow": { right: `${30}px` }, + ".cm-completionInfo.cm-completionInfo-right-narrow": { left: `${30}px` }, + "&light .cm-snippetField": { backgroundColor: "#00000022" }, + "&dark .cm-snippetField": { backgroundColor: "#ffffff22" }, + ".cm-snippetFieldPosition": { + verticalAlign: "text-top", + width: 0, + height: "1.15em", + display: "inline-block", + margin: "0 -0.7px -.7em", + borderLeft: "1.4px dotted #888" + }, + ".cm-completionMatchedText": { + textDecoration: "underline" + }, + ".cm-completionDetail": { + marginLeft: "0.5em", + fontStyle: "italic" + }, + ".cm-completionIcon": { + fontSize: "90%", + width: ".8em", + display: "inline-block", + textAlign: "center", + paddingRight: ".6em", + opacity: "0.6", + boxSizing: "content-box" + }, + ".cm-completionIcon-function, .cm-completionIcon-method": { + "&:after": { content: "'\u0192'" } + }, + ".cm-completionIcon-class": { + "&:after": { content: "'\u25CB'" } + }, + ".cm-completionIcon-interface": { + "&:after": { content: "'\u25CC'" } + }, + ".cm-completionIcon-variable": { + "&:after": { content: "'\u{1D465}'" } + }, + ".cm-completionIcon-constant": { + "&:after": { content: "'\u{1D436}'" } + }, + ".cm-completionIcon-type": { + "&:after": { content: "'\u{1D461}'" } + }, + ".cm-completionIcon-enum": { + "&:after": { content: "'\u222A'" } + }, + ".cm-completionIcon-property": { + "&:after": { content: "'\u25A1'" } + }, + ".cm-completionIcon-keyword": { + "&:after": { content: "'\u{1F511}\uFE0E'" } + // Disable emoji rendering + }, + ".cm-completionIcon-namespace": { + "&:after": { content: "'\u25A2'" } + }, + ".cm-completionIcon-text": { + "&:after": { content: "'abc'", fontSize: "50%", verticalAlign: "middle" } + } +}); +var defaults = { + brackets: ["(", "[", "{", "'", '"'], + before: ")]}:;>", + stringPrefixes: [] +}; +var closeBracketEffect = /* @__PURE__ */ import_state2.StateEffect.define({ + map(value, mapping) { + let mapped = mapping.mapPos(value, -1, import_state2.MapMode.TrackAfter); + return mapped == null ? void 0 : mapped; + } +}); +var closedBracket = /* @__PURE__ */ new class extends import_state2.RangeValue { +}(); +closedBracket.startSide = 1; +closedBracket.endSide = -1; +var bracketState = /* @__PURE__ */ import_state2.StateField.define({ + create() { + return import_state2.RangeSet.empty; + }, + update(value, tr) { + if (tr.selection) { + let lineStart = tr.state.doc.lineAt(tr.selection.main.head).from; + let prevLineStart = tr.startState.doc.lineAt(tr.startState.selection.main.head).from; + if (lineStart != tr.changes.mapPos(prevLineStart, -1)) + value = import_state2.RangeSet.empty; + } + value = value.map(tr.changes); + for (let effect of tr.effects) + if (effect.is(closeBracketEffect)) + value = value.update({ add: [closedBracket.range(effect.value, effect.value + 1)] }); + return value; + } +}); +function closeBrackets() { + return [inputHandler, bracketState]; +} +var definedClosing = "()[]{}<>"; +function closing(ch) { + for (let i = 0; i < definedClosing.length; i += 2) + if (definedClosing.charCodeAt(i) == ch) + return definedClosing.charAt(i + 1); + return (0, import_state2.fromCodePoint)(ch < 128 ? ch : ch + 1); +} +function config(state, pos) { + return state.languageDataAt("closeBrackets", pos)[0] || defaults; +} +var android = typeof navigator == "object" && /* @__PURE__ */ /Android\b/.test(navigator.userAgent); +var inputHandler = /* @__PURE__ */ import_view2.EditorView.inputHandler.of((view, from, to, insert) => { + if ((android ? view.composing : view.compositionStarted) || view.state.readOnly) + return false; + let sel = view.state.selection.main; + if (insert.length > 2 || insert.length == 2 && (0, import_state2.codePointSize)((0, import_state2.codePointAt)(insert, 0)) == 1 || from != sel.from || to != sel.to) + return false; + let tr = insertBracket(view.state, insert); + if (!tr) + return false; + view.dispatch(tr); + return true; +}); +var deleteBracketPair = ({ state, dispatch }) => { + if (state.readOnly) + return false; + let conf = config(state, state.selection.main.head); + let tokens = conf.brackets || defaults.brackets; + let dont = null, changes = state.changeByRange((range) => { + if (range.empty) { + let before = prevChar(state.doc, range.head); + for (let token of tokens) { + if (token == before && nextChar(state.doc, range.head) == closing((0, import_state2.codePointAt)(token, 0))) + return { + changes: { from: range.head - token.length, to: range.head + token.length }, + range: import_state2.EditorSelection.cursor(range.head - token.length) + }; + } + } + return { range: dont = range }; + }); + if (!dont) + dispatch(state.update(changes, { scrollIntoView: true, userEvent: "delete.backward" })); + return !dont; +}; +var closeBracketsKeymap = [ + { key: "Backspace", run: deleteBracketPair } +]; +function insertBracket(state, bracket) { + let conf = config(state, state.selection.main.head); + let tokens = conf.brackets || defaults.brackets; + for (let tok of tokens) { + let closed = closing((0, import_state2.codePointAt)(tok, 0)); + if (bracket == tok) + return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1, conf) : handleOpen(state, tok, closed, conf.before || defaults.before); + if (bracket == closed && closedBracketAt(state, state.selection.main.from)) + return handleClose(state, tok, closed); + } + return null; +} +function closedBracketAt(state, pos) { + let found = false; + state.field(bracketState).between(0, state.doc.length, (from) => { + if (from == pos) + found = true; + }); + return found; +} +function nextChar(doc, pos) { + let next = doc.sliceString(pos, pos + 2); + return next.slice(0, (0, import_state2.codePointSize)((0, import_state2.codePointAt)(next, 0))); +} +function prevChar(doc, pos) { + let prev = doc.sliceString(pos - 2, pos); + return (0, import_state2.codePointSize)((0, import_state2.codePointAt)(prev, 0)) == prev.length ? prev : prev.slice(1); +} +function handleOpen(state, open, close, closeBefore) { + let dont = null, changes = state.changeByRange((range) => { + if (!range.empty) + return { + changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }], + effects: closeBracketEffect.of(range.to + open.length), + range: import_state2.EditorSelection.range(range.anchor + open.length, range.head + open.length) + }; + let next = nextChar(state.doc, range.head); + if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1) + return { + changes: { insert: open + close, from: range.head }, + effects: closeBracketEffect.of(range.head + open.length), + range: import_state2.EditorSelection.cursor(range.head + open.length) + }; + return { range: dont = range }; + }); + return dont ? null : state.update(changes, { + scrollIntoView: true, + userEvent: "input.type" + }); +} +function handleClose(state, _open, close) { + let dont = null, changes = state.changeByRange((range) => { + if (range.empty && nextChar(state.doc, range.head) == close) + return { + changes: { from: range.head, to: range.head + close.length, insert: close }, + range: import_state2.EditorSelection.cursor(range.head + close.length) + }; + return dont = { range }; + }); + return dont ? null : state.update(changes, { + scrollIntoView: true, + userEvent: "input.type" + }); +} +function handleSame(state, token, allowTriple, config3) { + let stringPrefixes = config3.stringPrefixes || defaults.stringPrefixes; + let dont = null, changes = state.changeByRange((range) => { + if (!range.empty) + return { + changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }], + effects: closeBracketEffect.of(range.to + token.length), + range: import_state2.EditorSelection.range(range.anchor + token.length, range.head + token.length) + }; + let pos = range.head, next = nextChar(state.doc, pos), start; + if (next == token) { + if (nodeStart(state, pos)) { + return { + changes: { insert: token + token, from: pos }, + effects: closeBracketEffect.of(pos + token.length), + range: import_state2.EditorSelection.cursor(pos + token.length) + }; + } else if (closedBracketAt(state, pos)) { + let isTriple = allowTriple && state.sliceDoc(pos, pos + token.length * 3) == token + token + token; + let content = isTriple ? token + token + token : token; + return { + changes: { from: pos, to: pos + content.length, insert: content }, + range: import_state2.EditorSelection.cursor(pos + content.length) + }; + } + } else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token && (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 && nodeStart(state, start)) { + return { + changes: { insert: token + token + token + token, from: pos }, + effects: closeBracketEffect.of(pos + token.length), + range: import_state2.EditorSelection.cursor(pos + token.length) + }; + } else if (state.charCategorizer(pos)(next) != import_state2.CharCategory.Word) { + if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes)) + return { + changes: { insert: token + token, from: pos }, + effects: closeBracketEffect.of(pos + token.length), + range: import_state2.EditorSelection.cursor(pos + token.length) + }; + } + return { range: dont = range }; + }); + return dont ? null : state.update(changes, { + scrollIntoView: true, + userEvent: "input.type" + }); +} +function nodeStart(state, pos) { + let tree = (0, import_language3.syntaxTree)(state).resolveInner(pos + 1); + return tree.parent && tree.from == pos; +} +function probablyInString(state, pos, quoteToken, prefixes) { + let node = (0, import_language3.syntaxTree)(state).resolveInner(pos, -1); + let maxPrefix = prefixes.reduce((m, p) => Math.max(m, p.length), 0); + for (let i = 0; i < 5; i++) { + let start = state.sliceDoc(node.from, Math.min(node.to, node.from + quoteToken.length + maxPrefix)); + let quotePos = start.indexOf(quoteToken); + if (!quotePos || quotePos > -1 && prefixes.indexOf(start.slice(0, quotePos)) > -1) { + let first = node.firstChild; + while (first && first.from == node.from && first.to - first.from > quoteToken.length + quotePos) { + if (state.sliceDoc(first.to - quoteToken.length, first.to) == quoteToken) + return false; + first = first.firstChild; + } + return true; + } + let parent = node.to == pos && node.parent; + if (!parent) + break; + node = parent; + } + return false; +} +function canStartStringAt(state, pos, prefixes) { + let charCat = state.charCategorizer(pos); + if (charCat(state.sliceDoc(pos - 1, pos)) != import_state2.CharCategory.Word) + return pos; + for (let prefix of prefixes) { + let start = pos - prefix.length; + if (state.sliceDoc(start, pos) == prefix && charCat(state.sliceDoc(start - 1, start)) != import_state2.CharCategory.Word) + return start; + } + return -1; +} +function autocompletion(config3 = {}) { + return [ + completionState, + completionConfig.of(config3), + completionPlugin, + completionKeymapExt, + baseTheme + ]; +} +var completionKeymap = [ + { key: "Ctrl-Space", run: startCompletion }, + { key: "Escape", run: closeCompletion }, + { key: "ArrowDown", run: /* @__PURE__ */ moveCompletionSelection(true) }, + { key: "ArrowUp", run: /* @__PURE__ */ moveCompletionSelection(false) }, + { key: "PageDown", run: /* @__PURE__ */ moveCompletionSelection(true, "page") }, + { key: "PageUp", run: /* @__PURE__ */ moveCompletionSelection(false, "page") }, + { key: "Enter", run: acceptCompletion } +]; +var completionKeymapExt = /* @__PURE__ */ import_state2.Prec.highest(/* @__PURE__ */ import_view2.keymap.computeN([completionConfig], (state) => state.facet(completionConfig).defaultKeymap ? [completionKeymap] : [])); + +// src/codemirror-extensions/basic-extensions.ts +var import_search2 = require("@codemirror/search"); +var import_lint = require("@codemirror/lint"); + +// src/codemirror-extensions/obsidian-theme.ts +var import_view3 = require("@codemirror/view"); +var import_language4 = require("@codemirror/language"); +var import_highlight2 = require("@lezer/highlight"); +var config2 = { + name: "obsidian", + dark: false, + background: "var(--background-primary)", + foreground: "var(--text-normal)", + selection: "var(--text-selection)", + cursor: "var(--text-normal)", + dropdownBackground: "var(--background-primary)", + dropdownBorder: "var(--background-modifier-border)", + activeLine: "var(--background-primary)", + matchingBracket: "var(--background-modifier-accent)", + keyword: "#d73a49", + storage: "#d73a49", + variable: "var(--text-normal)", + parameter: "var(--text-accent-hover)", + function: "var(--text-accent-hover)", + string: "var(--text-accent)", + constant: "var(--text-accent-hover)", + type: "var(--text-accent-hover)", + class: "#6f42c1", + number: "var(--text-accent-hover)", + comment: "var(--text-faint)", + invalid: "var(--text-error)", + regexp: "#032f62" +}; +var obsidianTheme = import_view3.EditorView.theme( + { + "&": { + color: config2.foreground, + backgroundColor: config2.background + }, + ".cm-content": { caretColor: config2.cursor }, + "&.cm-focused .cm-cursor": { borderLeftColor: config2.cursor }, + "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, & ::selection": { backgroundColor: config2.selection }, + ".cm-panels": { + backgroundColor: config2.dropdownBackground, + color: config2.foreground + }, + ".cm-panels.cm-panels-top": { borderBottom: "2px solid black" }, + ".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" }, + ".cm-searchMatch": { + backgroundColor: config2.dropdownBackground, + outline: `1px solid ${config2.dropdownBorder}` + }, + ".cm-searchMatch.cm-searchMatch-selected": { + backgroundColor: config2.selection + }, + ".cm-activeLine": { backgroundColor: config2.activeLine }, + ".cm-activeLineGutter": { backgroundColor: config2.background }, + ".cm-selectionMatch": { backgroundColor: config2.selection }, + ".cm-matchingBracket, .cm-nonmatchingBracket": { + backgroundColor: config2.matchingBracket, + outline: "none" + }, + ".cm-gutters": { + backgroundColor: config2.background, + color: config2.comment, + borderRight: "1px solid var(--background-modifier-border)" + }, + ".cm-lineNumbers, .cm-gutterElement": { color: "inherit" }, + ".cm-foldPlaceholder": { + backgroundColor: "transparent", + border: "none", + color: config2.foreground + }, + ".cm-tooltip": { + border: `1px solid ${config2.dropdownBorder}`, + backgroundColor: config2.dropdownBackground, + color: config2.foreground + }, + ".cm-tooltip.cm-tooltip-autocomplete": { + "& > ul > li[aria-selected]": { + background: config2.selection, + color: config2.foreground + } + } + }, + { dark: config2.dark } +); +var obsidianHighlightStyle = import_language4.HighlightStyle.define([ + { tag: import_highlight2.tags.keyword, color: config2.keyword }, + { + tag: [import_highlight2.tags.name, import_highlight2.tags.deleted, import_highlight2.tags.character, import_highlight2.tags.macroName], + color: config2.variable + }, + { tag: [import_highlight2.tags.propertyName], color: config2.function }, + { + tag: [ + import_highlight2.tags.processingInstruction, + import_highlight2.tags.string, + import_highlight2.tags.inserted, + import_highlight2.tags.special(import_highlight2.tags.string) + ], + color: config2.string + }, + { tag: [import_highlight2.tags.function(import_highlight2.tags.variableName), import_highlight2.tags.labelName], color: config2.function }, + { + tag: [import_highlight2.tags.color, import_highlight2.tags.constant(import_highlight2.tags.name), import_highlight2.tags.standard(import_highlight2.tags.name)], + color: config2.constant + }, + { tag: [import_highlight2.tags.definition(import_highlight2.tags.name), import_highlight2.tags.separator], color: config2.variable }, + { tag: [import_highlight2.tags.className], color: config2.class }, + { + tag: [ + import_highlight2.tags.number, + import_highlight2.tags.changed, + import_highlight2.tags.annotation, + import_highlight2.tags.modifier, + import_highlight2.tags.self, + import_highlight2.tags.namespace + ], + color: config2.number + }, + { tag: [import_highlight2.tags.typeName], color: config2.type, fontStyle: config2.type }, + { tag: [import_highlight2.tags.operator, import_highlight2.tags.operatorKeyword], color: config2.keyword }, + { tag: [import_highlight2.tags.url, import_highlight2.tags.escape, import_highlight2.tags.regexp, import_highlight2.tags.link], color: config2.regexp }, + { tag: [import_highlight2.tags.meta, import_highlight2.tags.comment], color: config2.comment }, + { + tag: [import_highlight2.tags.atom, import_highlight2.tags.bool, import_highlight2.tags.special(import_highlight2.tags.variableName)], + color: config2.variable + }, + { tag: import_highlight2.tags.invalid, color: config2.invalid } +]); +var obsidian = [ + obsidianTheme, + (0, import_language4.syntaxHighlighting)(obsidianHighlightStyle) +]; + +// src/codemirror-extensions/basic-extensions.ts +var basicExtensions = [ + import_view4.keymap.of([ + ...closeBracketsKeymap, + // "{|}" -> backspace -> "|" + ...import_commands2.defaultKeymap, + ...import_search2.searchKeymap, + ...import_commands2.historyKeymap, + import_commands2.indentWithTab, + ...import_language5.foldKeymap, + ...completionKeymap, + ...import_lint.lintKeymap + ]), + (0, import_commands2.history)(), + css(), + (0, import_language5.foldGutter)(), + (0, import_view4.dropCursor)(), + import_state3.EditorState.allowMultipleSelections.of(true), + (0, import_language5.indentOnInput)(), + import_view4.EditorView.lineWrapping, + (0, import_language5.bracketMatching)(), + autocompletion(), + closeBrackets(), + (0, import_search2.highlightSelectionMatches)(), + obsidian +].filter((ext) => ext); + +// src/views/CssEditorView.ts +var VIEW_TYPE_CSS = "css-editor-view"; +var CssEditorView = class extends import_obsidian2.ItemView { + constructor(leaf) { + var _a, _b; + super(leaf); + this.file = null; + this.requestSave = (0, import_obsidian2.debounce)(this.save, 1e3); + this.navigation = true; + this.editor = new import_view5.EditorView({ + parent: this.contentEl, + extensions: [ + basicExtensions, + ((_b = (_a = this.app.vault).getConfig) == null ? void 0 : _b.call(_a, "vimMode")) ? vim() : [], + import_view5.EditorView.updateListener.of((update) => { + if (update.docChanged) { + this.requestSave(update.state.doc.toString()); + } + }) + ] + }); + } + getViewType() { + return VIEW_TYPE_CSS; + } + getIcon() { + return "file-code"; + } + getDisplayText() { + var _a, _b; + return (_b = (_a = this.file) == null ? void 0 : _a.basename) != null ? _b : ""; + } + async onOpen() { + const timer = window.setInterval(() => { + this.editor.focus(); + if (this.editor.hasFocus) + clearInterval(timer); + }, 200); + this.registerInterval(timer); + } + getEditorData() { + return this.editor.state.doc.toString(); + } + dispatchEditorData(data) { + this.editor.dispatch({ + changes: { + from: 0, + to: this.editor.state.doc.length, + insert: data + } + }); + } + getState() { + var _a; + return { + file: (_a = this.file) == null ? void 0 : _a.name + }; + } + async setState(state, result) { + var _a; + let file = null; + if (state && typeof state === "object") { + if ("filename" in state && typeof state.filename === "string") { + file = new CssFile(state.filename); + } + if ("file" in state) { + if (state.file instanceof CssFile) { + file = state.file; + } else if (typeof state.file === "string") { + file = new CssFile(state.file); + } + } + if (file && file.name !== ((_a = this.file) == null ? void 0 : _a.name)) { + const data = await readSnippetFile(this.app, file); + this.dispatchEditorData(data); + this.file = file; + this.app.workspace.requestSaveLayout(); + result.history = true; + } + } else { + result.history = true; + } + super.setState({ file: file == null ? void 0 : file.name }, result); + } + /** + * You should almost always call `requestSave` instead of `save` to debounce the saving. + */ + async save(data) { + if (this.file) { + writeSnippetFile(this.app, this.file, data); + } + } + async onClose() { + this.editor.destroy(); + } +}; + +// src/modals/CssSnippetFuzzySuggestModal.ts +var import_obsidian5 = require("obsidian"); + +// src/obsidian/workspace-helpers.ts +var import_obsidian3 = require("obsidian"); +async function openView(workspace, type, openInNewTab, state) { + const leaf = workspace.getLeaf(openInNewTab); + await leaf.setViewState({ + type, + state + }); + workspace.setActiveLeaf(leaf); +} +async function detachCssFileLeaves(workspace, file) { + var _a; + const leaves = workspace.getLeavesOfType(VIEW_TYPE_CSS); + for (const leaf of leaves) { + if ((0, import_obsidian3.requireApiVersion)("1.7.2")) { + await leaf.loadIfDeferred(); + } + if (((_a = leaf.getViewState().state) == null ? void 0 : _a.file) === file.name) { + leaf.detach(); + } + } +} + +// src/obsidian/Notice.ts +var import_obsidian4 = require("obsidian"); +var DEFAULT_NOTICE_TIMEOUT_SECONDS = 5; +var InfoNotice = class extends import_obsidian4.Notice { + constructor(message, timeout = DEFAULT_NOTICE_TIMEOUT_SECONDS) { + super(message, timeout * 1e3); + console.info(`css-editor: ${message}`); + } +}; +var ErrorNotice = class extends import_obsidian4.Notice { + constructor(message, timeout = DEFAULT_NOTICE_TIMEOUT_SECONDS) { + super(message, timeout * 1e3); + console.error(`css-editor: ${message}`); + } +}; + +// src/modals/CssSnippetFuzzySuggestModal.ts +var CssSnippetFuzzySuggestModal = class extends import_obsidian5.FuzzySuggestModal { + constructor(app, plugin) { + super(app); + this.plugin = plugin; + this.scope.register(["Mod"], "Enter", (evt) => { + var _a, _b; + if (!evt.isComposing && ((_b = (_a = this.chooser) == null ? void 0 : _a.useSelectedItem) == null ? void 0 : _b.call(_a, evt))) { + return false; + } + }); + this.scope.register(["Shift"], "Enter", (evt) => { + this.selectSuggestion( + { + item: new CssFile(this.inputEl.value), + match: { score: 0, matches: [] } + }, + evt + ); + return false; + }); + this.scope.register(["Mod"], "Delete", (evt) => { + var _a, _b; + if (!evt.isComposing && ((_b = (_a = this.chooser) == null ? void 0 : _a.useSelectedItem) == null ? void 0 : _b.call(_a, evt))) { + return false; + } + }); + this.scope.register([], "Tab", (evt) => { + if (this.chooser) { + const selItem = this.chooser.selectedItem; + const selSnippet = this.chooser.values[selItem].item; + const isEnabled = toggleSnippetFileState(this.app, selSnippet); + const selEl = this.chooser.suggestions[selItem].querySelector( + ".css-editor-status" + ); + selEl == null ? void 0 : selEl.setText(isEnabled ? "enabled" : "disabled"); + selEl == null ? void 0 : selEl.removeClass(isEnabled ? "disabled" : "enabled"); + selEl == null ? void 0 : selEl.addClass(isEnabled ? "enabled" : "disabled"); + } + return false; + }); + this.containerEl.addClass("css-editor-quick-switcher-modal"); + this.setPlaceholder("Find or create a CSS snippet..."); + this.setInstructions([ + { command: "\u2191\u2193", purpose: "to navigate" }, + { + command: import_obsidian5.Platform.isMacOS ? "\u2318 \u21B5" : "ctrl \u21B5", + purpose: "to open in new tab" + }, + { command: "shift \u21B5", purpose: "to create" }, + { + command: import_obsidian5.Platform.isMacOS ? "\u2318 del" : "ctrl del", + purpose: "to delete" + }, + { command: "tab", purpose: "to enable/disable" }, + { command: "esc", purpose: "to dismiss" } + ]); + } + isEnabled(item) { + var _a, _b; + const currentState = (_b = (_a = this.app.customCss) == null ? void 0 : _a.enabledSnippets) == null ? void 0 : _b.has( + item.basename + ); + return currentState || false; + } + getItems() { + var _a; + if ((_a = this.app.customCss) == null ? void 0 : _a.snippets) { + return this.app.customCss.snippets.map((x) => new CssFile(x)); + } + return []; + } + getItemText(item) { + return item.name; + } + renderSuggestion(item, el) { + super.renderSuggestion(item, el); + el.addClass("mod-complex"); + if (el.hasChildNodes()) { + const existingChildren = Array.from(el.childNodes); + el.childNodes.forEach((child) => { + el.removeChild(child); + }); + el.appendChild( + createDiv( + { cls: "suggestion-content" }, + (suggestionContentEl) => { + suggestionContentEl.appendChild( + createDiv({}, (nestedEl) => { + existingChildren.forEach((child) => { + nestedEl.appendChild(child); + }); + }) + ); + suggestionContentEl.appendChild( + createDiv( + { cls: "css-editor-suggestion-description" }, + (el2) => el2.appendText( + `${getSnippetDirectory(this.app)}${item.item.name}` + ) + ) + ); + } + ) + ); + const isEnabled = this.isEnabled(item.item); + const isNewElement = this.inputEl.value.trim().length > 0 && item.match.score === 0; + if (!isNewElement) { + el.appendChild( + createDiv( + { + cls: [ + "suggestion-aux", + "css-editor-status", + isEnabled ? "enabled" : "disabled" + ] + }, + (el2) => el2.appendText(isEnabled ? "enabled" : "disabled") + ) + ); + } + } + if (this.inputEl.value.trim().length > 0 && item.match.score === 0) { + el.appendChild( + createDiv({ cls: "suggestion-aux" }, (el2) => { + el2.appendChild( + createSpan({ cls: "suggestion-hotkey" }, (el3) => { + el3.appendText("Enter to create"); + }) + ); + }) + ); + } + } + async selectSuggestion(value, evt) { + try { + await this.onChooseSuggestion(value, evt); + this.close(); + } catch (err) { + if (err instanceof Error) { + new ErrorNotice(err.message); + } else { + new ErrorNotice("Failed to complete action. Reason unknown."); + } + } + } + async onChooseSuggestion(item, evt) { + const isCreateNewDueToNoSuggestion = this.inputEl.value.trim().length > 0 && item.match.score === 0; + if (isCreateNewDueToNoSuggestion && item.item) { + const openInNewTab = evt.metaKey; + await this.plugin.createAndOpenSnippet( + item.item.name, + openInNewTab + ); + } else { + await this.onChooseItem(item.item, evt); + } + } + onNoSuggestion() { + var _a, _b, _c, _d, _e, _f, _g, _h; + const item = this.inputEl.value.trim(); + if (item.length > 0) { + (_b = (_a = this.chooser) == null ? void 0 : _a.setSuggestions) == null ? void 0 : _b.call(_a, [ + { item: new CssFile(item), match: { score: 0, matches: [] } } + ]); + (_d = (_c = this.chooser) == null ? void 0 : _c.addMessage) == null ? void 0 : _d.call( + _c, + "No CSS snippets found. Enter to create a new one." + ); + } else { + (_f = (_e = this.chooser) == null ? void 0 : _e.setSuggestions) == null ? void 0 : _f.call(_e, []); + (_h = (_g = this.chooser) == null ? void 0 : _g.addMessage) == null ? void 0 : _h.call( + _g, + "No CSS snippets found. Type to search..." + ); + } + } + async onChooseItem(item, evt) { + if (!item) + return; + if (evt instanceof KeyboardEvent) { + if (evt.key === "Enter") { + const openInNewTab = evt.metaKey; + if (evt.shiftKey) { + await this.plugin.createAndOpenSnippet( + item.name, + openInNewTab + ); + } else { + openView(this.app.workspace, VIEW_TYPE_CSS, openInNewTab, { + file: item + }); + } + } else if (evt.key === "Delete") { + await detachCssFileLeaves(this.app.workspace, item); + await deleteSnippetFile(this.app, item); + new InfoNotice(`${item} was deleted.`); + } + } else { + const openInNewTab = evt.metaKey; + openView(this.app.workspace, VIEW_TYPE_CSS, openInNewTab, { + file: item + }); + } + } +}; + +// node_modules/monkey-around/mjs/index.js +function around(obj, factories) { + const removers = Object.keys(factories).map((key) => around1(obj, key, factories[key])); + return removers.length === 1 ? removers[0] : function() { + removers.forEach((r) => r()); + }; +} +function around1(obj, method, createWrapper) { + const original = obj[method], hadOwn = obj.hasOwnProperty(method); + let current = createWrapper(original); + if (original) + Object.setPrototypeOf(current, original); + Object.setPrototypeOf(wrapper, current); + obj[method] = wrapper; + return remove; + function wrapper(...args) { + if (current === original && obj[method] === wrapper) + remove(); + return current.apply(this, args); + } + function remove() { + if (obj[method] === wrapper) { + if (hadOwn) + obj[method] = original; + else + delete obj[method]; + } + if (current === original) + return; + current = original; + Object.setPrototypeOf(wrapper, original || Function); + } +} + +// src/obsidian/ignore-obsidian-hotkey.ts +function ignoreObsidianHotkey(keymapInfo, checkCallback) { + const uninstallCommand = around(this.app.scope, { + handleKey(originalMethod) { + return function(...args) { + const invokedHotkey = args[1]; + if (isKeymapInfo(invokedHotkey) && keymapInfo.key === invokedHotkey.key && keymapInfo.modifiers === invokedHotkey.modifiers && checkCallback()) { + return true; + } + const result = originalMethod && originalMethod.apply(this, args); + return result; + }; + } + }); + return uninstallCommand; +} +function isKeymapInfo(hotkey) { + return !!hotkey && typeof hotkey === "object" && "key" in hotkey && typeof hotkey.key === "string" && "modifiers" in hotkey; +} + +// src/modals/CssSnippetCreateModal.ts +var import_obsidian6 = require("obsidian"); +var CssSnippetCreateModal = class extends import_obsidian6.Modal { + constructor(app, plugin) { + super(app); + this.value = ""; + this.plugin = plugin; + } + onOpen() { + super.onOpen(); + this.titleEl.setText("Create CSS Snippet"); + this.containerEl.addClass("css-editor-create-modal"); + this.buildForm(); + } + buildForm() { + const textInput = new import_obsidian6.TextComponent(this.contentEl); + textInput.setPlaceholder("CSS snippet file name (ex: snippet.css)"); + textInput.onChange((val) => this.value = val); + textInput.inputEl.addEventListener("keydown", (evt) => { + this.handleKeydown(evt); + }); + } + async handleKeydown(evt) { + if (evt.key === "Escape") { + this.close(); + } else if (evt.key === "Enter") { + try { + const openInNewTab = evt.metaKey; + await this.plugin.createAndOpenSnippet( + this.value, + openInNewTab + ); + this.close(); + } catch (err) { + if (err instanceof Error) { + new ErrorNotice(err.message); + } else { + new ErrorNotice("Failed to create file. Reason unknown."); + } + } + } + } +}; + +// src/main.ts +var DEFAULT_SETTINGS = {}; +var CssEditorPlugin = class extends import_obsidian7.Plugin { + async onload() { + await this.loadSettings(); + this.addCommand({ + id: "create-css-snippet", + name: "Create CSS Snippet", + callback: async () => { + new CssSnippetCreateModal(this.app, this).open(); + } + }); + this.addCommand({ + id: "open-quick-switcher", + name: "Open quick switcher", + callback: async () => { + new CssSnippetFuzzySuggestModal(this.app, this).open(); + } + }); + this.addCommand({ + id: "delete-css-snippet", + name: "Delete current CSS Snippet", + checkCallback: (checking) => { + const activeCssEditorView = this.app.workspace.getActiveViewOfType(CssEditorView); + if (!activeCssEditorView) + return false; + if (checking) + return true; + const { file } = activeCssEditorView.getState(); + if (!file) + return; + const cssFile = new CssFile(file); + detachCssFileLeaves(this.app.workspace, cssFile).then( + async () => { + await deleteSnippetFile(this.app, cssFile); + new InfoNotice(`"${cssFile.name}" was deleted.`); + } + ); + } + }); + this.addCommand({ + id: "toggle-css-snippet-enabled-status", + name: "Toggle the enabled/disabled state of current CSS snippet", + checkCallback: (checking) => { + const activeCssEditorView = this.app.workspace.getActiveViewOfType(CssEditorView); + if (!activeCssEditorView) + return false; + if (checking) + return true; + const { file } = activeCssEditorView.getState(); + if (!file) + return; + const cssFile = new CssFile(file); + const isEnabled = toggleSnippetFileState(this.app, cssFile); + new InfoNotice( + `"${cssFile.name}" is now ${isEnabled ? "enabled" : "disabled"}.` + ); + } + }); + this.register( + ignoreObsidianHotkey( + { key: "/", modifiers: "Meta" }, + () => !!this.app.workspace.getActiveViewOfType(CssEditorView) + ) + ); + this.registerView(VIEW_TYPE_CSS, (leaf) => new CssEditorView(leaf)); + } + onunload() { + } + async loadSettings() { + this.settings = Object.assign( + {}, + DEFAULT_SETTINGS, + await this.loadData() + ); + } + async saveSettings() { + await this.saveData(this.settings); + } + async createAndOpenSnippet(filename, openInNewTab) { + var _a, _b; + const file = await createSnippetFile(this.app, filename, ""); + (_b = (_a = this.app.customCss) == null ? void 0 : _a.setCssEnabledStatus) == null ? void 0 : _b.call(_a, file.basename, true); + new InfoNotice(`${file.name} was created.`); + openView(this.app.workspace, VIEW_TYPE_CSS, openInNewTab, { + file + }); + } +}; + +/* nosourcemap */ \ No newline at end of file diff --git a/example-vault/rich-foot-example/.obsidian/plugins/css-editor/manifest.json b/example-vault/rich-foot-example/.obsidian/plugins/css-editor/manifest.json new file mode 100644 index 0000000..3575fc1 --- /dev/null +++ b/example-vault/rich-foot-example/.obsidian/plugins/css-editor/manifest.json @@ -0,0 +1,11 @@ +{ + "id": "css-editor", + "name": "CSS Editor", + "version": "1.2.2", + "minAppVersion": "0.15.0", + "description": "Edit CSS files within Obsidian.", + "author": "Zachatoo", + "authorUrl": "https://zachyoung.dev", + "fundingUrl": "https://github.com/sponsors/Zachatoo", + "isDesktopOnly": false +} diff --git a/example-vault/rich-foot-example/.obsidian/plugins/css-editor/styles.css b/example-vault/rich-foot-example/.obsidian/plugins/css-editor/styles.css new file mode 100644 index 0000000..874f7dd --- /dev/null +++ b/example-vault/rich-foot-example/.obsidian/plugins/css-editor/styles.css @@ -0,0 +1,21 @@ +.suggestion-item .css-editor-suggestion-description { + color: var(--text-muted); +} + +.css-editor-status { + border-radius: 5px; + border: 1px solid var(--color-base-50); + padding: 2px 6px; + + &.enabled { + background-color: var(--color-accent); + } + + &.disabled { + background-color: var(--background-secondary-alt); + } +} + +.css-editor-create-modal input { + width: 100%; +} diff --git a/example-vault/rich-foot-example/.obsidian/plugins/markdown-attributes/main.js b/example-vault/rich-foot-example/.obsidian/plugins/markdown-attributes/main.js new file mode 100644 index 0000000..632c928 --- /dev/null +++ b/example-vault/rich-foot-example/.obsidian/plugins/markdown-attributes/main.js @@ -0,0 +1,359 @@ +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ + +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); +var __publicField = (obj, key, value) => { + __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + return value; +}; +var __async = (__this, __arguments, generator) => { + return new Promise((resolve, reject) => { + var fulfilled = (value) => { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + }; + var rejected = (value) => { + try { + step(generator.throw(value)); + } catch (e) { + reject(e); + } + }; + var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); + step((generator = generator.apply(__this, __arguments)).next()); + }); +}; + +// src/main.ts +var main_exports = {}; +__export(main_exports, { + default: () => MarkdownAttributes +}); +module.exports = __toCommonJS(main_exports); +var import_obsidian2 = require("obsidian"); + +// src/processor.ts +var _Processor = class { + constructor() { + } + static parse(el) { + if (typeof el == "string") { + return new _Processor().parseLine(el); + } else { + return new _Processor().recurseAndParseElements(el); + } + } + parseLine(text) { + const elements = []; + let attribute_strings = text.matchAll( + new RegExp(_Processor.END_RE.source, "gm") + ); + for (const [_, match] of attribute_strings) { + elements.push({ + attributes: this.getAttrs(match), + text: match + }); + } + return elements; + } + getTopLevelText(el) { + const texts = []; + for (let child of Array.from(el.childNodes)) { + if (child.nodeType == Node.TEXT_NODE) { + texts.push(child.data); + } + } + return texts.join(""); + } + getAttrs(str) { + const trys = (str != null ? str : "").split(/\s(?=(?:[^'"`]*(['"`])[^'"`]*\1)*[^'"`]*$)/).map((t) => t && t.trim()).filter((t) => t && t !== '"' && t !== "'" && t.length); + if (!trys || !trys.length) + return; + const allowedKeyChars = /[^\t\n\f />"'=]/; + const keySeparator = "="; + const classChar = "."; + const attrs = []; + for (let pair of trys) { + if (!pair || !pair.length) + continue; + if (pair.charAt(0) === classChar) { + attrs.push(["class", pair.slice(1)]); + continue; + } + if (new RegExp(keySeparator).test(pair) && allowedKeyChars.test(pair.slice(0, pair.indexOf(keySeparator)))) { + attrs.push([...pair.split(keySeparator, 2)]); + continue; + } + attrs.push([pair, null]); + } + return attrs; + } + recurseAndParseElements(el) { + var _a, _b, _c; + const elements = []; + const text = this.getTopLevelText(el); + if (_Processor.BLOCK_RE.test(text)) { + let element = el; + if (el instanceof HTMLLIElement || (el == null ? void 0 : el.parentElement) instanceof HTMLQuoteElement || (el == null ? void 0 : el.hasClass("callout"))) { + element = el.parentElement; + } + let [original, attribute_string] = (_a = text.match(_Processor.BLOCK_RE)) != null ? _a : []; + const toAdd = { + element, + attributes: this.getAttrs(attribute_string), + text: attribute_string + }; + elements.push(toAdd); + el.innerHTML = this.tryToReplace( + toAdd.element, + el.innerHTML, + toAdd.attributes, + original + ); + if (el instanceof HTMLLIElement) { + elements.push(...this.recurseAndParseElements(el)); + } + } else if (_Processor.BASE_RE.test(text)) { + let textNode = Array.from(el.childNodes).find( + (node) => node.nodeType == Node.TEXT_NODE && _Processor.BASE_RE.test(text) + ); + let sibling = (_b = Array.from(el.children).find( + (node) => node.nextSibling == textNode + )) != null ? _b : el; + if (sibling && sibling.hasClass("collapse-indicator")) { + sibling = sibling.parentElement; + } + if (sibling && sibling instanceof HTMLBRElement) { + sibling = sibling.parentElement; + } + let [original, attribute_string] = (_c = text.match(_Processor.BASE_RE)) != null ? _c : []; + const toAdd = { + element: sibling, + attributes: this.getAttrs(attribute_string), + text: attribute_string + }; + elements.push(toAdd); + textNode.textContent = this.tryToReplace( + toAdd.element, + textNode.textContent, + toAdd.attributes, + original + ); + } + for (let child of Array.from(el.children)) { + if (!(child instanceof HTMLElement)) + continue; + if (child instanceof HTMLPreElement || child.tagName.toLowerCase() === "code") + continue; + elements.push(...this.recurseAndParseElements(child)); + } + return elements; + } + tryToReplace(element, content, attributes, original) { + if (!attributes || !attributes.length) { + return content; + } + for (let [key, value] of attributes) { + if (!key) + continue; + if (value) + value = value.replace(/("|')/g, ""); + try { + if (key === "class") { + element.addClasses(value.split(" ")); + } else if (!value) { + element.setAttr(key, true); + } else { + element.setAttr(key, value); + } + } catch (e) { + console.log( + `Markdown Attributes: ${key} is not a valid attribute.` + ); + return content; + } + } + return content.replace(original, ""); + } +}; +var Processor = _Processor; +__publicField(Processor, "BASE_RE", /\{\:?[ ]*([^\}\n ][^\}\n]*)[ ]*\}/); +__publicField(Processor, "ONLY_RE", /^\{\:?[ ]*([^\}\n ][^\}\n]*)[ ]*\}$/); +__publicField(Processor, "BLOCK_RE", /\n[ ]*\{\:?[ ]*([^\}\n ][^\}\n]*)[ ]*\}[ ]*$/); +__publicField(Processor, "END_RE", /\{\:?[ ]*([^\}\n ][^\}\n]*)[ ]*\}$/m); + +// src/live-preview.ts +var import_view = require("@codemirror/view"); +var import_language = require("@codemirror/language"); +var import_obsidian = require("obsidian"); +function selectionAndRangeOverlap(selection, rangeFrom, rangeTo) { + for (const range of selection.ranges) { + if (range.from <= rangeTo && range.to >= rangeFrom) { + return true; + } + } + return false; +} +function inlineRender(view) { + if (!view.state.field(import_obsidian.editorLivePreviewField)) { + this.decorations = import_view.Decoration.none; + return; + } + const currentFile = app.workspace.getActiveFile(); + if (!currentFile) + return; + const widgets = []; + const selection = view.state.selection; + for (const { from, to } of view.visibleRanges) { + (0, import_language.syntaxTree)(view.state).iterate({ + from, + to, + enter: ({ node }) => { + var _a; + const type = node.type; + if (type.name.includes("formatting")) + return; + const start = node.from; + const end = node.to; + if (selectionAndRangeOverlap(selection, start - 1, end + 1)) + return; + const original = view.state.doc.sliceString(start, end).trim(); + if (!Processor.END_RE.test(original)) + return; + const parsed = (_a = Processor.parse(original)) != null ? _a : []; + for (const item of parsed) { + const { attributes, text } = item; + const firstBracket = original.slice(0, original.indexOf(text)).lastIndexOf("{"); + const lastBracket = original.indexOf( + "}", + original.indexOf(text) + ); + widgets.push( + import_view.Decoration.replace({ + inclusive: false, + block: false + }).range(start + firstBracket, start + lastBracket + 1), + import_view.Decoration.mark({ + inclusive: true, + attributes: Object.fromEntries(attributes) + }).range(start, end) + ); + } + } + }); + } + return import_view.Decoration.set(widgets, true); +} +function inlinePlugin() { + return import_view.ViewPlugin.fromClass( + class { + constructor(view) { + __publicField(this, "decorations"); + var _a; + this.decorations = (_a = inlineRender(view)) != null ? _a : import_view.Decoration.none; + } + update(update) { + var _a; + if (!update.state.field(import_obsidian.editorLivePreviewField)) { + this.decorations = import_view.Decoration.none; + return; + } + if (update.docChanged || update.viewportChanged || update.selectionSet) { + this.decorations = (_a = inlineRender(update.view)) != null ? _a : import_view.Decoration.none; + } + } + }, + { decorations: (v) => v.decorations } + ); +} + +// src/main.ts +var MarkdownAttributes = class extends import_obsidian2.Plugin { + constructor() { + super(...arguments); + __publicField(this, "parsing", /* @__PURE__ */ new Map()); + } + onload() { + return __async(this, null, function* () { + console.log(`Markdown Attributes v${this.manifest.version} loaded.`); + this.registerMarkdownPostProcessor(this.postprocessor.bind(this)); + this.registerEditorExtension(inlinePlugin()); + }); + } + postprocessor(topElement, ctx) { + return __async(this, null, function* () { + var _a, _b, _c; + const child = topElement.firstElementChild; + if (!child) + return; + let str; + if (child instanceof HTMLPreElement) { + if (!ctx.getSectionInfo(topElement)) + return; + const { lineStart } = ctx.getSectionInfo(topElement); + const file = this.app.vault.getAbstractFileByPath(ctx.sourcePath); + if (!(file instanceof import_obsidian2.TFile)) + return; + const text = yield this.app.vault.cachedRead(file); + let source = text.split("\n").slice(lineStart, lineStart + 1); + str = source.join("\n"); + if (!Processor.BASE_RE.test(str)) + return; + let [attribute_string] = (_a = str.match(Processor.BASE_RE)) != null ? _a : []; + child.prepend(new Text(attribute_string)); + } + if (child instanceof HTMLTableElement || child.hasClass("math") && child.hasClass("math-block") || child.hasClass("callout")) { + if (!ctx.getSectionInfo(topElement)) + return; + const { text, lineEnd } = ctx.getSectionInfo(topElement); + const adjustment = child.hasClass("callout") ? 0 : 1; + let source = ((_b = text.split("\n").slice(lineEnd + adjustment, lineEnd + adjustment + 1)) != null ? _b : []).shift(); + if (source && source.length && Processor.ONLY_RE.test(source.trim())) { + let [attribute_string] = (_c = source.match(Processor.ONLY_RE)) != null ? _c : []; + child.prepend(new Text(attribute_string)); + str = topElement.innerText; + } + } + if (child instanceof HTMLParagraphElement && !child.childElementCount) { + if (Processor.ONLY_RE.test(child.innerText.trim())) { + child.detach(); + return; + } + } + if (!Processor.BASE_RE.test(str != null ? str : topElement.innerText)) + return; + if (!(child instanceof HTMLElement)) + return; + Processor.parse(child); + }); + } + onunload() { + return __async(this, null, function* () { + console.log("Markdown Attributes unloaded"); + }); + } +}; + +/* nosourcemap */ \ No newline at end of file diff --git a/example-vault/rich-foot-example/.obsidian/plugins/markdown-attributes/manifest.json b/example-vault/rich-foot-example/.obsidian/plugins/markdown-attributes/manifest.json new file mode 100644 index 0000000..482af85 --- /dev/null +++ b/example-vault/rich-foot-example/.obsidian/plugins/markdown-attributes/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "markdown-attributes", + "name": "Markdown Attributes", + "version": "1.2.2", + "minAppVersion": "0.12.10", + "description": "Add markdown attributes to elements in Obsidian.md", + "author": "Jeremy Valentine", + "authorUrl": "", + "isDesktopOnly": false +} diff --git a/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/data.json b/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/data.json new file mode 100644 index 0000000..8d14684 --- /dev/null +++ b/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/data.json @@ -0,0 +1,17 @@ +{ + "frames": [ + { + "url": "https://jparkerweb.github.io/release-notes/", + "displayName": "📄 release notes", + "icon": "notebook-pen", + "hideOnMobile": true, + "addRibbonIcon": true, + "openInCenter": true, + "zoomLevel": 1, + "forceIframe": false, + "customCss": "", + "customJs": "" + } + ], + "padding": 5 +} \ No newline at end of file diff --git a/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/main.js b/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/main.js new file mode 100644 index 0000000..894186f --- /dev/null +++ b/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/main.js @@ -0,0 +1,635 @@ +/* +THIS IS A GENERATED/BUNDLED FILE BY ESBUILD +if you want to view the source, please visit the github repository of this plugin +*/ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); +var __export = (target, all) => { + __markAsModule(target); + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __reExport = (target, module2, desc) => { + if (module2 && typeof module2 === "object" || typeof module2 === "function") { + for (let key of __getOwnPropNames(module2)) + if (!__hasOwnProp.call(target, key) && key !== "default") + __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable }); + } + return target; +}; +var __toModule = (module2) => { + return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2); +}; +var __async = (__this, __arguments, generator) => { + return new Promise((resolve, reject) => { + var fulfilled = (value) => { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + }; + var rejected = (value) => { + try { + step(generator.throw(value)); + } catch (e) { + reject(e); + } + }; + var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); + step((generator = generator.apply(__this, __arguments)).next()); + }); +}; + +// src/main.ts +__export(exports, { + default: () => CustomFramesPlugin +}); +var import_obsidian4 = __toModule(require("obsidian")); + +// src/frame.ts +var import_obsidian = __toModule(require("obsidian")); + +// src/settings.ts +var defaultSettings = { + frames: [], + padding: 5 +}; +var presets = { + "obsidian": { + url: "https://forum.obsidian.md/", + displayName: "Obsidian Forum", + icon: "edit", + hideOnMobile: true, + addRibbonIcon: true, + openInCenter: true, + zoomLevel: 1, + forceIframe: false, + customCss: "", + customJs: "" + }, + "detexify": { + url: "https://detexify.kirelabs.org/classify.html", + displayName: "Detexify", + icon: "type", + hideOnMobile: true, + addRibbonIcon: true, + openInCenter: false, + zoomLevel: 0.95, + forceIframe: false, + customCss: `/* hide info clutter and ad banner */ +#classify--info-area, +.adsbygoogle { + display: none !important +}`, + customJs: "" + }, + "calendar": { + url: "https://calendar.google.com/calendar", + displayName: "Google Calendar", + icon: "calendar", + hideOnMobile: true, + addRibbonIcon: true, + openInCenter: true, + zoomLevel: 1, + forceIframe: false, + customCss: `/* hide the menu bar "Calendar" text and remove minimum width */ +div[style*="min-width: 238px"] { + min-width: 0 !important; + padding-right: 0 !important; +} +div[style*="min-width: 238px"] span[role*="heading"] { + display: none !important; +}`, + customJs: "" + }, + "keep": { + url: "https://keep.google.com", + displayName: "Google Keep", + icon: "files", + hideOnMobile: true, + addRibbonIcon: false, + openInCenter: false, + zoomLevel: 1, + forceIframe: false, + customCss: `/* hide the menu bar, the "Keep" text and the Google Apps button */ +html > body > div:nth-child(2) > div:nth-child(2) > div:first-child, +html > body > div:first-child > header:first-child > div > div:first-child > div > div:first-child > a:first-child > span, +html > body > div:first-child > header:first-child > div:nth-child(2) > div:first-child > div:first-child, +html > body > div:first-child > header:first-child > div:nth-child(2) > div:nth-child(3) > div:first-child > div:first-child > div:first-child { + display: none !important; +} +html > body > div:first-child > header:first-child > div > div:first-child > div > div:first-child > a:first-child { + cursor: default; +}`, + customJs: "" + }, + "todoist": { + url: "https://todoist.com", + displayName: "Todoist", + icon: "list-checks", + hideOnMobile: true, + addRibbonIcon: false, + openInCenter: false, + zoomLevel: 1, + forceIframe: false, + customCss: `/* hide the help, home, search, and productivity overview buttons, create extra space, and prevent toast pop-up from acting weird */ +[aria-label="Go to Home view"], #quick_find, [aria-label="Productivity"], [aria-label="Help & Feedback"] { + display: none !important; +} + +.view_content { + padding-left: 15px; +} + +.view_header { + padding-left: 15px; + padding-top: 10px; +} + +.undo_toast { + width: 95%; +}`, + customJs: "" + }, + "notion": { + url: "https://www.notion.so/", + displayName: "Notion", + icon: "box", + hideOnMobile: true, + addRibbonIcon: true, + openInCenter: true, + zoomLevel: 1, + forceIframe: false, + customCss: "", + customJs: "" + }, + "twitter": { + url: "https://twitter.com", + displayName: "Twitter", + icon: "twitter", + hideOnMobile: true, + addRibbonIcon: false, + openInCenter: false, + zoomLevel: 1, + forceIframe: false, + customCss: "", + customJs: "" + }, + "tasks": { + url: "https://tasks.google.com/embed/?origin=https://calendar.google.com&fullWidth=1", + displayName: "Google Tasks", + icon: "list-checks", + hideOnMobile: true, + addRibbonIcon: false, + openInCenter: false, + zoomLevel: 1, + forceIframe: false, + customCss: "", + customJs: "" + } +}; +function getIcon(settings) { + return settings.icon ? `lucide-${settings.icon}` : "documents"; +} +function getId(settings) { + return settings.displayName.toLowerCase().replace(/\s/g, "-"); +} + +// src/frame.ts +var CustomFrame = class { + constructor(settings, data) { + this.settings = settings; + this.data = data; + } + create(parent, additionalStyle = void 0, urlSuffix = void 0) { + let style = `padding: ${this.settings.padding}px;`; + if (additionalStyle) + style += additionalStyle; + if (import_obsidian.Platform.isDesktopApp && !this.data.forceIframe) { + let frameDoc = parent.doc; + this.frame = frameDoc.createElement("webview"); + parent.appendChild(this.frame); + this.frame.setAttribute("allowpopups", ""); + this.frame.addEventListener("dom-ready", () => { + this.frame.setZoomFactor(this.data.zoomLevel); + this.frame.insertCSS(this.data.customCss); + this.frame.executeJavaScript(this.data.customJs); + }); + this.frame.addEventListener("destroyed", () => { + if (frameDoc != parent.doc) { + this.frame.detach(); + this.create(parent, additionalStyle, urlSuffix); + } + }); + } else { + this.frame = parent.doc.createElement("iframe"); + parent.appendChild(this.frame); + this.frame.setAttribute("sandbox", "allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts allow-top-navigation-by-user-activation allow-downloads"); + this.frame.setAttribute("allow", "encrypted-media; fullscreen; oversized-images; picture-in-picture; sync-xhr; geolocation;"); + style += `transform: scale(${this.data.zoomLevel}); transform-origin: 0 0;`; + } + this.frame.addClass("custom-frames-frame"); + this.frame.addClass(`custom-frames-${getId(this.data)}`); + this.frame.setAttribute("style", style); + let src = this.data.url; + if (urlSuffix) { + if (!urlSuffix.startsWith("/")) + src += "/"; + src += urlSuffix; + } + this.frame.setAttribute("src", src); + } + refresh() { + if (this.frame instanceof HTMLIFrameElement) { + this.frame.contentWindow.location.reload(); + } else { + this.frame.reload(); + } + } + return() { + if (this.frame instanceof HTMLIFrameElement) { + this.frame.contentWindow.open(this.data.url); + } else { + this.frame.loadURL(this.data.url); + } + } + goBack() { + if (this.frame instanceof HTMLIFrameElement) { + this.frame.contentWindow.history.back(); + } else { + this.frame.goBack(); + } + } + goForward() { + if (this.frame instanceof HTMLIFrameElement) { + this.frame.contentWindow.history.forward(); + } else { + this.frame.goForward(); + } + } + toggleDevTools() { + if (!(this.frame instanceof HTMLIFrameElement)) { + if (!this.frame.isDevToolsOpened()) { + this.frame.openDevTools(); + } else { + this.frame.closeDevTools(); + } + } + } + getCurrentUrl() { + return this.frame instanceof HTMLIFrameElement ? this.frame.contentWindow.location.href : this.frame.getURL(); + } + focus() { + if (this.frame instanceof HTMLIFrameElement) { + this.frame.contentWindow.focus(); + } else { + this.frame.focus(); + } + } +}; + +// src/settings-tab.ts +var import_obsidian2 = __toModule(require("obsidian")); +var CustomFramesSettingTab = class extends import_obsidian2.PluginSettingTab { + constructor(app, plugin) { + super(app, plugin); + this.plugin = plugin; + } + display() { + this.containerEl.empty(); + this.containerEl.createEl("h2", { text: "Custom Frames Settings" }); + this.containerEl.createEl("p", { + text: "Please note that Obsidian has to be restarted or reloaded for most of these settings to take effect.", + cls: "mod-warning" + }); + new import_obsidian2.Setting(this.containerEl).setName("Frame Padding").setDesc("The padding that should be left around the inside of custom frame panes, in pixels.").addText((t) => { + t.inputEl.type = "number"; + t.setValue(String(this.plugin.settings.padding)); + t.onChange((v) => __async(this, null, function* () { + this.plugin.settings.padding = v.length ? Number(v) : defaultSettings.padding; + yield this.plugin.saveSettings(); + })); + }); + for (let frame of this.plugin.settings.frames) { + let heading = this.containerEl.createEl("h3", { text: frame.displayName || "Unnamed Frame" }); + let toggle = new import_obsidian2.ButtonComponent(this.containerEl).setButtonText("Show Settings").setClass("custom-frames-show").onClick(() => __async(this, null, function* () { + content.hidden = !content.hidden; + toggle.setButtonText(content.hidden ? "Show Settings" : "Hide Settings"); + })); + let content = this.containerEl.createDiv(); + content.hidden = true; + new import_obsidian2.Setting(content).setName("Display Name").setDesc("The display name that this frame should have.").addText((t) => { + t.setValue(frame.displayName); + t.onChange((v) => __async(this, null, function* () { + frame.displayName = v; + heading.setText(frame.displayName || "Unnamed Frame"); + yield this.plugin.saveSettings(); + })); + }); + new import_obsidian2.Setting(content).setName("Icon").setDesc(createFragment((f) => { + f.createSpan({ text: "The icon that this frame's pane should have. The names of any " }); + f.createEl("a", { text: "Lucide icons", href: "https://lucide.dev/" }); + f.createSpan({ text: " can be used." }); + })).addText((t) => { + t.setValue(frame.icon); + t.onChange((v) => __async(this, null, function* () { + frame.icon = v; + yield this.plugin.saveSettings(); + })); + }); + new import_obsidian2.Setting(content).setName("URL").setDesc("The URL that should be opened in this frame.").addText((t) => { + t.setValue(frame.url); + t.onChange((v) => __async(this, null, function* () { + frame.url = v; + yield this.plugin.saveSettings(); + })); + }); + new import_obsidian2.Setting(content).setName("Disable on Mobile").setDesc("Custom Frames is a lot more restricted on mobile devices and doesn't allow for the same types of content to be displayed. If a frame doesn't work as expected on mobile, it can be disabled.").addToggle((t) => { + t.setValue(frame.hideOnMobile); + t.onChange((v) => __async(this, null, function* () { + frame.hideOnMobile = v; + yield this.plugin.saveSettings(); + })); + }); + new import_obsidian2.Setting(content).setName("Add Ribbon Icon").setDesc("Whether a button to open this frame should be added to the ribbon.").addToggle((t) => { + t.setValue(frame.addRibbonIcon); + t.onChange((v) => __async(this, null, function* () { + frame.addRibbonIcon = v; + yield this.plugin.saveSettings(); + })); + }); + new import_obsidian2.Setting(content).setName("Open in Center").setDesc("Whether this frame should be opened in the unpinned center editor rather than one of the panes on the side. This is useful for sites that don't work well in a narrow view, or sites that don't require a note to be open when viewed.").addToggle((t) => { + t.setValue(frame.openInCenter); + t.onChange((v) => __async(this, null, function* () { + frame.openInCenter = v; + yield this.plugin.saveSettings(); + })); + }); + new import_obsidian2.Setting(content).setName("Force iframe").setDesc(createFragment((f) => { + f.createSpan({ text: "Whether this frame should use iframes on desktop as opposed to Electron webviews." }); + f.createEl("br"); + f.createEl("em", { text: "Only enable this setting if the frame is causing issues or frequent crashes. This setting causes all Desktop-only settings to be ignored." }); + })).addToggle((t) => { + t.setValue(frame.forceIframe); + t.onChange((v) => __async(this, null, function* () { + frame.forceIframe = v; + yield this.plugin.saveSettings(); + })); + }); + new import_obsidian2.Setting(content).setName("Page Zoom").setDesc("The zoom that this frame's page should be displayed with, as a percentage.").addText((t) => { + t.inputEl.type = "number"; + t.setValue(String(frame.zoomLevel * 100)); + t.onChange((v) => __async(this, null, function* () { + frame.zoomLevel = v.length ? Number(v) / 100 : 1; + yield this.plugin.saveSettings(); + })); + }); + new import_obsidian2.Setting(content).setName("Additional CSS").setDesc(createFragment((f) => { + f.createSpan({ text: "A snippet of additional CSS that should be applied to this frame." }); + f.createEl("br"); + f.createEl("em", { text: "Note that this is only applied on Desktop." }); + })).addTextArea((t) => { + t.inputEl.rows = 5; + t.inputEl.cols = 50; + t.setValue(frame.customCss); + t.onChange((v) => __async(this, null, function* () { + frame.customCss = v; + yield this.plugin.saveSettings(); + })); + }); + new import_obsidian2.Setting(content).setName("Additional JavaScript").setDesc(createFragment((f) => { + f.createSpan({ text: "A snippet of additional JavaScript that should be applied to this frame." }); + f.createEl("br"); + f.createEl("em", { text: "Note that this is only applied on Desktop." }); + })).addTextArea((t) => { + t.inputEl.rows = 5; + t.inputEl.cols = 50; + t.setValue(frame.customJs); + t.onChange((v) => __async(this, null, function* () { + frame.customJs = v; + yield this.plugin.saveSettings(); + })); + }); + new import_obsidian2.ButtonComponent(content).setButtonText("Remove Frame").onClick(() => __async(this, null, function* () { + this.plugin.settings.frames.remove(frame); + yield this.plugin.saveSettings(); + this.display(); + })); + } + this.containerEl.createEl("hr"); + this.containerEl.createEl("p", { text: `Create a new frame, either from a preset shipped with the plugin, or a custom one that you can edit yourself. Each frame's pane can be opened using the "Custom Frames: Open" command.` }); + let addDiv = this.containerEl.createDiv(); + let dropdown = new import_obsidian2.DropdownComponent(addDiv); + dropdown.addOption("new", "Custom"); + for (let key of Object.keys(presets)) + dropdown.addOption(key, presets[key].displayName); + new import_obsidian2.ButtonComponent(addDiv).setButtonText("Add Frame").setClass("custom-frames-add").onClick(() => __async(this, null, function* () { + let option = dropdown.getValue(); + if (option == "new") { + this.plugin.settings.frames.push({ + url: "", + displayName: "New Frame", + icon: "", + hideOnMobile: true, + addRibbonIcon: false, + openInCenter: false, + zoomLevel: 1, + forceIframe: false, + customCss: "", + customJs: "" + }); + } else { + this.plugin.settings.frames.push(presets[option]); + } + yield this.plugin.saveSettings(); + this.display(); + })); + let disclaimer = this.containerEl.createEl("p", { cls: "mod-warning" }); + disclaimer.createSpan({ text: "Please be advised that, when adding a site as a custom frame, you potentially expose personal information you enter to other plugins you have installed. For more information, see " }); + disclaimer.createEl("a", { + text: "this discussion", + href: "https://github.com/Ellpeck/ObsidianCustomFrames/issues/54#issuecomment-1210879685", + cls: "mod-warning" + }); + disclaimer.createSpan({ text: "." }); + this.containerEl.createEl("hr"); + this.containerEl.createEl("p", { text: "If you like this plugin and want to support its development, you can do so through my website by clicking this fancy image!" }); + this.containerEl.createEl("a", { href: "https://ellpeck.de/support" }).createEl("img", { + attr: { src: "https://ellpeck.de/res/generalsupport.png" }, + cls: "custom-frames-support" + }); + } +}; + +// src/view.ts +var import_obsidian3 = __toModule(require("obsidian")); +var _CustomFrameView = class extends import_obsidian3.ItemView { + constructor(leaf, settings, data, name) { + super(leaf); + this.data = data; + this.name = name; + this.frame = new CustomFrame(settings, data); + this.navigation = data.openInCenter; + for (let action of _CustomFrameView.actions) + this.addAction(action.icon, action.name, () => action.action(this)); + } + onload() { + this.contentEl.empty(); + this.contentEl.addClass("custom-frames-view"); + this.frame.create(this.contentEl); + } + onPaneMenu(menu, source) { + super.onPaneMenu(menu, source); + for (let action of _CustomFrameView.actions) { + menu.addItem((i) => { + i.setTitle(action.name); + i.setIcon(action.icon); + i.onClick(() => action.action(this)); + }); + } + } + getViewType() { + return this.name; + } + getDisplayText() { + return this.data.displayName; + } + getIcon() { + return getIcon(this.data); + } + focus() { + this.frame.focus(); + } +}; +var CustomFrameView = _CustomFrameView; +CustomFrameView.actions = [ + { + name: "Return to original page", + icon: "home", + action: (v) => v.frame.return() + }, + { + name: "Open dev tools", + icon: "binary", + action: (v) => v.frame.toggleDevTools() + }, + { + name: "Copy link", + icon: "link", + action: (v) => navigator.clipboard.writeText(v.frame.getCurrentUrl()) + }, + { + name: "Open in browser", + icon: "globe", + action: (v) => open(v.frame.getCurrentUrl()) + }, + { + name: "Refresh", + icon: "refresh-cw", + action: (v) => v.frame.refresh() + }, + { + name: "Go back", + icon: "arrow-left", + action: (v) => v.frame.goBack() + }, + { + name: "Go forward", + icon: "arrow-right", + action: (v) => v.frame.goForward() + } +]; + +// src/main.ts +var CustomFramesPlugin = class extends import_obsidian4.Plugin { + onload() { + return __async(this, null, function* () { + yield this.loadSettings(); + for (let frame of this.settings.frames) { + if (!frame.url || !frame.displayName) + continue; + let name = `custom-frames-${getId(frame)}`; + if (import_obsidian4.Platform.isMobileApp && frame.hideOnMobile) { + console.log(`Skipping frame ${name} which is hidden on mobile`); + continue; + } + try { + console.log(`Registering frame ${name} for URL ${frame.url}`); + this.registerView(name, (l) => new CustomFrameView(l, this.settings, frame, name)); + this.addCommand({ + id: `open-${name}`, + name: `Open ${frame.displayName}`, + callback: () => this.openLeaf(name, frame.openInCenter, false) + }); + if (frame.addRibbonIcon) + this.addRibbonIcon(getIcon(frame), `Open ${frame.displayName}`, (e) => this.openLeaf(name, frame.openInCenter, import_obsidian4.Platform.isMacOS ? e.metaKey : e.ctrlKey)); + } catch (e) { + console.error(`Couldn't register frame ${name}, is there already one with the same name?`); + } + } + this.addSettingTab(new CustomFramesSettingTab(this.app, this)); + this.registerMarkdownCodeBlockProcessor("custom-frames", (s, e) => { + e.empty(); + e.addClass("custom-frames-view-file"); + let frameMatch = /frame:([^\n]+)/gi.exec(s); + let frameName = frameMatch && frameMatch[1].trim(); + if (!frameName) { + e.createSpan({ text: "Couldn't parse frame name" }); + return; + } + let data = this.settings.frames.find((f) => f.displayName == frameName); + if (!data) { + e.createSpan({ text: `Couldn't find a frame with name ${frameName}` }); + return; + } + if (import_obsidian4.Platform.isMobileApp && data.hideOnMobile) { + e.createSpan({ text: `${frameName} is hidden on mobile` }); + return; + } + let styleMatch = /style:([^\n]+)/gi.exec(s); + let style = styleMatch && styleMatch[1].trim(); + style || (style = "height: 600px;"); + let urlSuffixMatch = /urlsuffix:([^\n]+)/gi.exec(s); + let urlSuffix = urlSuffixMatch && urlSuffixMatch[1].trim(); + urlSuffix || (urlSuffix = ""); + let frame = new CustomFrame(this.settings, data); + frame.create(e, style, urlSuffix); + }); + }); + } + loadSettings() { + return __async(this, null, function* () { + this.settings = Object.assign({}, defaultSettings, yield this.loadData()); + }); + } + saveSettings() { + return __async(this, null, function* () { + yield this.saveData(this.settings); + }); + } + openLeaf(name, center, split) { + return __async(this, null, function* () { + let leaf; + if (center) { + leaf = this.app.workspace.getLeaf(split); + yield leaf.setViewState({ type: name, active: true }); + } else { + if (!this.app.workspace.getLeavesOfType(name).length) + yield this.app.workspace.getRightLeaf(false).setViewState({ type: name, active: true }); + leaf = this.app.workspace.getLeavesOfType(name)[0]; + this.app.workspace.revealLeaf(leaf); + } + if (leaf.view instanceof CustomFrameView) + leaf.view.focus(); + }); + } +}; + + +/* nosourcemap */ \ No newline at end of file diff --git a/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/manifest.json b/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/manifest.json new file mode 100644 index 0000000..a16aad3 --- /dev/null +++ b/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "obsidian-custom-frames", + "name": "Custom Frames", + "version": "2.4.7", + "minAppVersion": "1.2.0", + "description": "A plugin that turns web apps into panes using iframes with custom styling. Also comes with presets for Google Keep, Todoist and more.", + "author": "Ellpeck", + "authorUrl": "https://ellpeck.de", + "isDesktopOnly": false +} diff --git a/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/styles.css b/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/styles.css new file mode 100644 index 0000000..e9ec246 --- /dev/null +++ b/example-vault/rich-foot-example/.obsidian/plugins/obsidian-custom-frames/styles.css @@ -0,0 +1,31 @@ +.custom-frames-view { + padding: 0 !important; + overflow: hidden !important; +} + +.custom-frames-view-file { + padding: 0; + overflow: auto; +} + +.custom-frames-frame { + width: 100%; + height: 100%; + border: none; + background-color: white; + background-clip: content-box; +} + +.custom-frames-add { + margin-left: 10px; +} + +.custom-frames-show { + margin-bottom: 18px; +} + +.custom-frames-support { + max-width: 50%; + width: 400px; + height: auto; +} diff --git a/example-vault/rich-foot-example/.obsidian/plugins/pexels-banner/data.json b/example-vault/rich-foot-example/.obsidian/plugins/pexels-banner/data.json index dc7a245..01244cb 100644 --- a/example-vault/rich-foot-example/.obsidian/plugins/pexels-banner/data.json +++ b/example-vault/rich-foot-example/.obsidian/plugins/pexels-banner/data.json @@ -45,8 +45,8 @@ "showReleaseNotes": true, "lastVersion": "2.10.1", "showRefreshIcon": true, - "hidePixelBannerFields": false, - "hidePropertiesSectionIfOnlyBanner": false, + "hidePixelBannerFields": true, + "hidePropertiesSectionIfOnlyBanner": true, "titleColor": "var(--inline-title-color)", "customTitleColorField": [ "banner-inline-title-color" diff --git a/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/data.json b/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/data.json index 950f562..19c7f28 100644 --- a/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/data.json +++ b/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/data.json @@ -14,8 +14,10 @@ "linkColor": "var(--link-color)", "linkBackgroundColor": "var(--tag-background)", "linkBorderColor": "rgba(255, 255, 255, 0.204)", + "customCreatedDateProp": "created", + "customModifiedDateProp": "modified", "showBacklinks": true, "showOutlinks": true, "showDates": true, - "lastVersion": "1.6.2" + "lastVersion": "1.6.0" } \ No newline at end of file diff --git a/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/main.js b/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/main.js index 8a1ac5f..1385e6a 100644 --- a/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/main.js +++ b/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/main.js @@ -46,17 +46,45 @@ var ReleaseNotesModal = class extends import_obsidian.Modal { text: "After each update you'll be prompted with the release notes. You can disable this in the plugin settings.", cls: "release-notes-instructions" }); - const kofiContainer = contentEl.createEl("div"); - kofiContainer.style.textAlign = "right"; - const kofiLink = kofiContainer.createEl("a", { - href: "https://ko-fi.com/jparkerweb", + const promotionalLinks = contentEl.createEl("div"); + promotionalLinks.style.display = "flex"; + promotionalLinks.style.flexDirection = "row"; + promotionalLinks.style.justifyContent = "space-around"; + const equilllabsLink = promotionalLinks.createEl("a", { + href: "https://www.equilllabs.com", + target: "_blank" + }); + equilllabsLink.createEl("img", { + attr: { + height: "36", + style: "border:0px;height:36px;", + src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/equilllabs.png?raw=true", + border: "0", + alt: "eQuill-Labs" + } + }); + const discordLink = promotionalLinks.createEl("a", { + href: "https://discord.gg/sp8AQQhMJ7", + target: "_blank" + }); + discordLink.createEl("img", { + attr: { + height: "36", + style: "border:0px;height:36px;", + src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/discord.png?raw=true", + border: "0", + alt: "Discord" + } + }); + const kofiLink = promotionalLinks.createEl("a", { + href: "https://ko-fi.com/Z8Z212UMBI", target: "_blank" }); kofiLink.createEl("img", { attr: { height: "36", style: "border:0px;height:36px;", - src: "https://raw.githubusercontent.com/jparkerweb/rich-foot/refs/heads/main/img/support.png", + src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/support.png?raw=true", border: "0", alt: "Buy Me a Coffee at ko-fi.com" } @@ -79,7 +107,7 @@ var ReleaseNotesModal = class extends import_obsidian.Modal { }; // virtual-module:virtual:release-notes -var releaseNotes = '

\u{1F389} What's New

\n

v1.6.2

\n

Updated

\n
    \n
  • outlinks section to inclued transclusion links (example [[note#section]] or [text](note#section))
  • \n
\n

v1.6.1

\n

Fixed

\n
    \n
  • Fixed console error when switching between reading/editing modes
  • \n
\n

v1.6.0

\n

New Color Customization Options

\n
    \n
  • New Border, Links, and Date color customization options in settings
      \n
    • Color picker to select custom colors
    • \n
    • Reset button to restore default colors (theme accent color)
    • \n
    • Real-time color updates
    • \n
    \n
  • \n
\n

New Color Customization Options

\n'; +var releaseNotes = '

\u{1F4C6} Dates Your Way

\n

v1.7.0

\n

\u2728 Added

\n
    \n
  • Custom Created/Modified Date Property fields to allow users to specify their own frontmatter properties for dates, useful when file system dates are affected by sync processes and you track them separately.
  • \n
\n

screenshot

\n'; // src/main.js var DEFAULT_SETTINGS = { @@ -95,7 +123,9 @@ var DEFAULT_SETTINGS = { borderColor: "var(--text-accent)", linkColor: "var(--link-color)", linkBackgroundColor: "var(--tag-background)", - linkBorderColor: "rgba(255, 255, 255, 0.204)" + linkBorderColor: "rgba(255, 255, 255, 0.204)", + customCreatedDateProp: "", + customModifiedDateProp: "" }; function rgbToHex(color) { if (color.startsWith("hsl")) { @@ -307,17 +337,29 @@ var RichFootPlugin = class extends import_obsidian2.Plugin { } if (this.settings.showDates) { const datesWrapper = richFoot.createDiv({ cls: "rich-foot--dates-wrapper" }); - const fileUpdate = new Date(file.stat.mtime); - const modified = `${fileUpdate.toLocaleString("default", { month: "long" })} ${fileUpdate.getDate()}, ${fileUpdate.getFullYear()}`; + const cache = this.app.metadataCache.getFileCache(file); + const frontmatter = cache == null ? void 0 : cache.frontmatter; + let modifiedDate; + if (this.settings.customModifiedDateProp && frontmatter && frontmatter[this.settings.customModifiedDateProp]) { + modifiedDate = frontmatter[this.settings.customModifiedDateProp]; + } else { + modifiedDate = new Date(file.stat.mtime); + modifiedDate = `${modifiedDate.toLocaleString("default", { month: "long" })} ${modifiedDate.getDate()}, ${modifiedDate.getFullYear()}`; + } datesWrapper.createDiv({ cls: "rich-foot--modified-date", - text: `${modified}` + text: `${modifiedDate}` }); - const fileCreated = new Date(file.stat.ctime); - const created = `${fileCreated.toLocaleString("default", { month: "long" })} ${fileCreated.getDate()}, ${fileCreated.getFullYear()}`; + let createdDate; + if (this.settings.customCreatedDateProp && frontmatter && frontmatter[this.settings.customCreatedDateProp]) { + createdDate = frontmatter[this.settings.customCreatedDateProp]; + } else { + createdDate = new Date(file.stat.ctime); + createdDate = `${createdDate.toLocaleString("default", { month: "long" })} ${createdDate.getDate()}, ${createdDate.getFullYear()}`; + } datesWrapper.createDiv({ cls: "rich-foot--created-date", - text: `${created}` + text: `${createdDate}` }); } return richFoot; @@ -400,6 +442,8 @@ var RichFootSettingTab = class extends import_obsidian2.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; + this.createdDateInput = null; + this.modifiedDateInput = null; } display() { var _a; @@ -461,6 +505,40 @@ var RichFootSettingTab = class extends import_obsidian2.PluginSettingTab { await this.plugin.saveSettings(); this.plugin.updateRichFoot(); })); + containerEl.createEl("h3", { text: "Date Settings" }); + new import_obsidian2.Setting(containerEl).setName("Show Dates").setDesc("Show creation and modification dates in the footer").addToggle((toggle) => toggle.setValue(this.plugin.settings.showDates).onChange(async (value) => { + this.plugin.settings.showDates = value; + await this.plugin.saveSettings(); + this.plugin.updateRichFoot(); + })); + new import_obsidian2.Setting(containerEl).setName("Custom Created Date Property").setDesc("Specify a frontmatter property to use for creation date (leave empty to use file creation date)").addText((text) => { + text.setValue(this.plugin.settings.customCreatedDateProp).onChange(async (value) => { + this.plugin.settings.customCreatedDateProp = value; + await this.plugin.saveSettings(); + this.plugin.updateRichFoot(); + }); + this.createdDateInput = text; + return text; + }).addButton((button) => button.setButtonText("Reset").onClick(async () => { + this.plugin.settings.customCreatedDateProp = ""; + await this.plugin.saveSettings(); + this.plugin.updateRichFoot(); + this.createdDateInput.setValue(""); + })); + new import_obsidian2.Setting(containerEl).setName("Custom Modified Date Property").setDesc("Specify a frontmatter property to use for modification date (leave empty to use file modification date)").addText((text) => { + text.setValue(this.plugin.settings.customModifiedDateProp).onChange(async (value) => { + this.plugin.settings.customModifiedDateProp = value; + await this.plugin.saveSettings(); + this.plugin.updateRichFoot(); + }); + this.modifiedDateInput = text; + return text; + }).addButton((button) => button.setButtonText("Reset").onClick(async () => { + this.plugin.settings.customModifiedDateProp = ""; + await this.plugin.saveSettings(); + this.plugin.updateRichFoot(); + this.modifiedDateInput.setValue(""); + })); containerEl.createEl("h3", { text: "Style Settings" }); new import_obsidian2.Setting(containerEl).setName("Border Width").setDesc("Adjust the width of the footer border (1-10px)").addSlider((slider) => slider.setLimits(1, 10, 1).setValue(this.plugin.settings.borderWidth).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.borderWidth = value; diff --git a/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/manifest.json b/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/manifest.json index d322ff2..75890a4 100644 --- a/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/manifest.json +++ b/example-vault/rich-foot-example/.obsidian/plugins/rich-foot/manifest.json @@ -1,7 +1,7 @@ { "id": "rich-foot", "name": "Rich Foot", - "version": "1.6.2", + "version": "1.7.0", "minAppVersion": "1.5.0", "description": "Adds backlink tags and created/modified dates to the footer of your notes.", "author": "Justin Parker (eQui\\\\ Labs)", diff --git a/example-vault/rich-foot-example/.obsidian/snippets/utility.css b/example-vault/rich-foot-example/.obsidian/snippets/utility.css new file mode 100644 index 0000000..ebc94be --- /dev/null +++ b/example-vault/rich-foot-example/.obsidian/snippets/utility.css @@ -0,0 +1,67 @@ +/* General Utilities */ +.margin-0 { margin: 0 !important; } +.margin-left-right-auto { + margin-left: auto; + margin-right: auto; +} + +.padding-20 { padding: 20px; } + +.border-radius { border-radius: 17px; } + +.width-small { + width: 200px; + max-width: 200px; +} + +.float-right { float: right; } + +.color-yellow { color: yellow !important; } +.color-white { color: white; } + +.bg-black { background: black !important; } +.bg-shaded { background: #00000070; } + +.display-block { display: block; } +.display-inline-block { display: inline-block; } + +.top-right { + position: absolute; + top: 10px; + right: 10px; +} + +/* Flex Utilities */ +.flex-row { + display: flex; + flex-direction: row; + align-items: center; +} + +.flex-column { + display: flex; + flex-direction: column; + justify-content: center; +} + +.flex-center { + align-items: center; + justify-content: center; +} + +/* Specific Components */ +.release-title { + border-radius: 17px; /* Matches consolidated value */ + background: #00000070; /* Matches bg-shaded */ + padding: 20px; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; +} + +/* Markdown Styles */ +.markdown-rendered code { + color: var(--interactive-accent); + filter: brightness(150%); +} diff --git a/example-vault/rich-foot-example/.obsidian/types.json b/example-vault/rich-foot-example/.obsidian/types.json index c7ce6a1..7f7cdb3 100644 --- a/example-vault/rich-foot-example/.obsidian/types.json +++ b/example-vault/rich-foot-example/.obsidian/types.json @@ -3,6 +3,8 @@ "aliases": "aliases", "cssclasses": "multitext", "tags": "tags", - "links": "multitext" + "links": "multitext", + "created": "text", + "modified": "text" } } \ No newline at end of file diff --git a/example-vault/rich-foot-example/.obsidian/workspace.json b/example-vault/rich-foot-example/.obsidian/workspace.json index ae387af..e9304e9 100644 --- a/example-vault/rich-foot-example/.obsidian/workspace.json +++ b/example-vault/rich-foot-example/.obsidian/workspace.json @@ -106,15 +106,47 @@ "type": "leaf", "state": { "type": "outline", + "state": {}, + "icon": "lucide-list", + "title": "Outline" + } + }, + { + "id": "46d47ecd473a3665", + "type": "leaf", + "state": { + "type": "file-properties", + "state": {}, + "icon": "lucide-info", + "title": "File properties" + } + }, + { + "id": "8b76c4ae7238dba8", + "type": "leaf", + "state": { + "type": "all-properties", "state": { - "file": "backlink test.md" + "sortOrder": "frequency", + "showSearch": false, + "searchQuery": "" }, - "icon": "lucide-list", - "title": "Outline of backlink test" + "icon": "lucide-archive", + "title": "All properties" + } + }, + { + "id": "52fd00eedba22e1b", + "type": "leaf", + "state": { + "type": "empty", + "state": {}, + "icon": "lucide-file", + "title": "New tab" } }, { - "id": "4508dfcd1d199dfa", + "id": "d79a08e8670c4660", "type": "leaf", "state": { "type": "empty", @@ -124,7 +156,7 @@ } }, { - "id": "388b22a3a54ccc4c", + "id": "31c3f2d74db82215", "type": "leaf", "state": { "type": "empty", @@ -134,7 +166,7 @@ } }, { - "id": "fc7ebc193edee888", + "id": "8d18b4a09cbaab4e", "type": "leaf", "state": { "type": "empty", @@ -144,7 +176,7 @@ } }, { - "id": "86780c664be3375b", + "id": "4674f52fab9186d3", "type": "leaf", "state": { "type": "Saved Queries View", @@ -154,15 +186,16 @@ } } ], - "currentTab": 5 + "currentTab": 8 } ], "direction": "horizontal", - "width": 300, + "width": 454.5, "collapsed": true }, "left-ribbon": { "hiddenItems": { + "obsidian-custom-frames:Open 📄 release notes": false, "cmdr:Open Plugin Settings: Pixel Banner": false, "cmdr:Open Plugin Settings: Rich Foot": false, "cmdr:Reload app without saving": false, @@ -173,10 +206,12 @@ }, "active": "e2b1b7584f7a474c", "lastOpenFiles": [ - "backlink test.md", - "exclude/no rich-feet here.md", + "releases/v1.7.0 - 📆 Dates Your Way.md", "🦶 Rich Foot.md", + "pixel-banner-images/calendar-feet.png", + "exclude/no rich-feet here.md", "exclude/me too/no rich-feet here either.md", + "backlink test.md", "images/rich-feet-3.jpg", "images/rich-feet-2.jpg", "images/rich-feet.jpg", diff --git a/example-vault/rich-foot-example/pixel-banner-images/calendar-feet.png b/example-vault/rich-foot-example/pixel-banner-images/calendar-feet.png new file mode 100644 index 0000000..81abee7 Binary files /dev/null and b/example-vault/rich-foot-example/pixel-banner-images/calendar-feet.png differ diff --git "a/example-vault/rich-foot-example/releases/v1.7.0 - \360\237\223\206 Dates Your Way.md" "b/example-vault/rich-foot-example/releases/v1.7.0 - \360\237\223\206 Dates Your Way.md" new file mode 100644 index 0000000..2217e5a --- /dev/null +++ "b/example-vault/rich-foot-example/releases/v1.7.0 - \360\237\223\206 Dates Your Way.md" @@ -0,0 +1,14 @@ +--- +banner: "[[calendar-feet.png]]" +banner-height: "500" +banner-y: "30" +content-start: "250" +created: Super Cool Day (who knows when ❔) +modified: Cold day in Nov (2024-11-26) +--- + # 🦶 Rich Foot ⋅ v1.7.0 - 📆 Dates Your Way { .release-title } + +#### What's New 🎉 + +### ✨ Added +- Added `Custom Created/Modified Date Property` fields to allow users to specify their own frontmatter properties for dates, useful when file system dates are affected by sync processes and you track them separately. \ No newline at end of file diff --git a/img/releases/rich-foot-v1.7.0.jpg b/img/releases/rich-foot-v1.7.0.jpg new file mode 100644 index 0000000..37ad82a Binary files /dev/null and b/img/releases/rich-foot-v1.7.0.jpg differ diff --git a/manifest.json b/manifest.json index d322ff2..75890a4 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "rich-foot", "name": "Rich Foot", - "version": "1.6.2", + "version": "1.7.0", "minAppVersion": "1.5.0", "description": "Adds backlink tags and created/modified dates to the footer of your notes.", "author": "Justin Parker (eQui\\\\ Labs)", diff --git a/src/main.js b/src/main.js index 5a046aa..d633eb7 100644 --- a/src/main.js +++ b/src/main.js @@ -16,6 +16,8 @@ const DEFAULT_SETTINGS = { linkColor: 'var(--link-color)', linkBackgroundColor: 'var(--tag-background)', linkBorderColor: 'rgba(255, 255, 255, 0.204)', + customCreatedDateProp: '', + customModifiedDateProp: '', }; class RichFootSettings { @@ -36,6 +38,8 @@ class RichFootSettings { this.linkColor = DEFAULT_SETTINGS.linkColor; this.linkBackgroundColor = DEFAULT_SETTINGS.linkBackgroundColor; this.linkBorderColor = DEFAULT_SETTINGS.linkBorderColor; + this.customCreatedDateProp = DEFAULT_SETTINGS.customCreatedDateProp; + this.customModifiedDateProp = DEFAULT_SETTINGS.customModifiedDateProp; } } @@ -359,19 +363,33 @@ class RichFootPlugin extends Plugin { // Dates if (this.settings.showDates) { const datesWrapper = richFoot.createDiv({ cls: 'rich-foot--dates-wrapper' }); - - const fileUpdate = new Date(file.stat.mtime); - const modified = `${fileUpdate.toLocaleString('default', { month: 'long' })} ${fileUpdate.getDate()}, ${fileUpdate.getFullYear()}`; + const cache = this.app.metadataCache.getFileCache(file); + const frontmatter = cache?.frontmatter; + + // Modified date + let modifiedDate; + if (this.settings.customModifiedDateProp && frontmatter && frontmatter[this.settings.customModifiedDateProp]) { + modifiedDate = frontmatter[this.settings.customModifiedDateProp]; + } else { + modifiedDate = new Date(file.stat.mtime); + modifiedDate = `${modifiedDate.toLocaleString('default', { month: 'long' })} ${modifiedDate.getDate()}, ${modifiedDate.getFullYear()}`; + } datesWrapper.createDiv({ cls: 'rich-foot--modified-date', - text: `${modified}` + text: `${modifiedDate}` }); - const fileCreated = new Date(file.stat.ctime); - const created = `${fileCreated.toLocaleString('default', { month: 'long' })} ${fileCreated.getDate()}, ${fileCreated.getFullYear()}`; + // Created date + let createdDate; + if (this.settings.customCreatedDateProp && frontmatter && frontmatter[this.settings.customCreatedDateProp]) { + createdDate = frontmatter[this.settings.customCreatedDateProp]; + } else { + createdDate = new Date(file.stat.ctime); + createdDate = `${createdDate.toLocaleString('default', { month: 'long' })} ${createdDate.getDate()}, ${createdDate.getFullYear()}`; + } datesWrapper.createDiv({ cls: 'rich-foot--created-date', - text: `${created}` + text: `${createdDate}` }); } @@ -467,6 +485,8 @@ class RichFootSettingTab extends PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; + this.createdDateInput = null; + this.modifiedDateInput = null; } display() { @@ -573,6 +593,68 @@ class RichFootSettingTab extends PluginSettingTab { this.plugin.updateRichFoot(); })); + // Add Date Settings + containerEl.createEl('h3', { text: 'Date Settings' }); + + new Setting(containerEl) + .setName('Show Dates') + .setDesc('Show creation and modification dates in the footer') + .addToggle(toggle => toggle + .setValue(this.plugin.settings.showDates) + .onChange(async (value) => { + this.plugin.settings.showDates = value; + await this.plugin.saveSettings(); + this.plugin.updateRichFoot(); + })); + + new Setting(containerEl) + .setName('Custom Created Date Property') + .setDesc('Specify a frontmatter property to use for creation date (leave empty to use file creation date)') + .addText(text => { + text.setValue(this.plugin.settings.customCreatedDateProp) + .onChange(async (value) => { + this.plugin.settings.customCreatedDateProp = value; + await this.plugin.saveSettings(); + this.plugin.updateRichFoot(); + }); + // Store the text component for reset access + this.createdDateInput = text; + return text; + }) + .addButton(button => button + .setButtonText('Reset') + .onClick(async () => { + this.plugin.settings.customCreatedDateProp = ''; + await this.plugin.saveSettings(); + this.plugin.updateRichFoot(); + // Update the text input using the stored component + this.createdDateInput.setValue(''); + })); + + new Setting(containerEl) + .setName('Custom Modified Date Property') + .setDesc('Specify a frontmatter property to use for modification date (leave empty to use file modification date)') + .addText(text => { + text.setValue(this.plugin.settings.customModifiedDateProp) + .onChange(async (value) => { + this.plugin.settings.customModifiedDateProp = value; + await this.plugin.saveSettings(); + this.plugin.updateRichFoot(); + }); + // Store the text component for reset access + this.modifiedDateInput = text; + return text; + }) + .addButton(button => button + .setButtonText('Reset') + .onClick(async () => { + this.plugin.settings.customModifiedDateProp = ''; + await this.plugin.saveSettings(); + this.plugin.updateRichFoot(); + // Update the text input using the stored component + this.modifiedDateInput.setValue(''); + })); + // Border Settings containerEl.createEl('h3', { text: 'Style Settings' }); diff --git a/src/modals.js b/src/modals.js index 4a74cbf..d18cf72 100644 --- a/src/modals.js +++ b/src/modals.js @@ -21,19 +21,47 @@ export class ReleaseNotesModal extends Modal { cls: 'release-notes-instructions' }); - // Ko-fi container - const kofiContainer = contentEl.createEl('div'); - kofiContainer.style.textAlign = 'right'; + // Promotional links + const promotionalLinks = contentEl.createEl('div'); + promotionalLinks.style.display = 'flex'; + promotionalLinks.style.flexDirection = 'row'; + promotionalLinks.style.justifyContent = 'space-around'; - const kofiLink = kofiContainer.createEl('a', { - href: 'https://ko-fi.com/jparkerweb', + const equilllabsLink = promotionalLinks.createEl('a', { + href: 'https://www.equilllabs.com', + target: '_blank', + }); + equilllabsLink.createEl('img', { + attr: { + height: '36', + style: 'border:0px;height:36px;', + src: 'https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/equilllabs.png?raw=true', + border: '0', + alt: 'eQuill-Labs' + } + }); + const discordLink = promotionalLinks.createEl('a', { + href: 'https://discord.gg/sp8AQQhMJ7', + target: '_blank', + }); + discordLink.createEl('img', { + attr: { + height: '36', + style: 'border:0px;height:36px;', + src: 'https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/discord.png?raw=true', + border: '0', + alt: 'Discord' + } + }); + const kofiLink = promotionalLinks.createEl('a', { + href: 'https://ko-fi.com/Z8Z212UMBI', target: '_blank', }); kofiLink.createEl('img', { attr: { height: '36', style: 'border:0px;height:36px;', - src: 'https://raw.githubusercontent.com/jparkerweb/rich-foot/refs/heads/main/img/support.png', + src: 'https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/support.png?raw=true', border: '0', alt: 'Buy Me a Coffee at ko-fi.com' }