vim-sim 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -642,19 +642,19 @@ var FileSystemManager = class _FileSystemManager {
642
642
  /**
643
643
  * Create or open a file with the given path
644
644
  */
645
- openFile(path2, content = "", readonly = false) {
645
+ openFile(path, content = "", readonly = false) {
646
646
  const newFiles = new Map(this.files);
647
- if (newFiles.has(path2)) {
648
- return new _FileSystemManager(newFiles, path2);
647
+ if (newFiles.has(path)) {
648
+ return new _FileSystemManager(newFiles, path);
649
649
  }
650
650
  const file = {
651
- path: path2,
651
+ path,
652
652
  buffer: new Buffer(content),
653
653
  modified: false,
654
654
  readonly
655
655
  };
656
- newFiles.set(path2, file);
657
- return new _FileSystemManager(newFiles, path2);
656
+ newFiles.set(path, file);
657
+ return new _FileSystemManager(newFiles, path);
658
658
  }
659
659
  /**
660
660
  * Update the buffer content for the current file
@@ -739,9 +739,9 @@ var FileSystemManager = class _FileSystemManager {
739
739
  /**
740
740
  * Switch to a specific file by path
741
741
  */
742
- switchToFile(path2) {
743
- if (this.files.has(path2)) {
744
- return new _FileSystemManager(this.files, path2);
742
+ switchToFile(path) {
743
+ if (this.files.has(path)) {
744
+ return new _FileSystemManager(this.files, path);
745
745
  }
746
746
  return this;
747
747
  }
@@ -1304,8 +1304,6 @@ var SpellChecker = class _SpellChecker {
1304
1304
  };
1305
1305
 
1306
1306
  // src/types/CompletionManager.ts
1307
- import path from "path";
1308
- import * as fs from "fs";
1309
1307
  var CompletionManager = class _CompletionManager {
1310
1308
  state = null;
1311
1309
  constructor() {
@@ -1406,36 +1404,19 @@ var CompletionManager = class _CompletionManager {
1406
1404
  }
1407
1405
  /**
1408
1406
  * Start filename completion
1407
+ * Note: This is a stub in browser environments. File system access requires Node.js.
1409
1408
  */
1410
- async fileNameCompletion(buffer, cursorLine, cursorCol, currentDir = process.cwd()) {
1409
+ async fileNameCompletion(buffer, cursorLine, cursorCol, currentDir) {
1411
1410
  const prefix = this.getFilePathPrefix(buffer, cursorLine, cursorCol);
1412
1411
  if (prefix.length === 0) return [];
1413
- try {
1414
- const dirname = path.dirname(path.join(currentDir, prefix));
1415
- const basename = path.basename(prefix);
1416
- let files = [];
1417
- try {
1418
- files = await fs.promises.readdir(dirname);
1419
- } catch {
1420
- return [];
1421
- }
1422
- const matches = files.filter((file) => file.startsWith(basename)).map((file) => ({
1423
- text: path.join(path.dirname(prefix), file),
1424
- type: "filename" /* FILENAME */,
1425
- info: this.getFileTypeInfo(path.join(dirname, file))
1426
- }));
1427
- this.state = {
1428
- matches,
1429
- currentIndex: 0,
1430
- prefix,
1431
- type: "filename" /* FILENAME */,
1432
- isActive: prefix.length > 0
1433
- // Active if we have a prefix, even if no matches
1434
- };
1435
- return matches;
1436
- } catch (error) {
1437
- return [];
1438
- }
1412
+ this.state = {
1413
+ matches: [],
1414
+ currentIndex: 0,
1415
+ prefix,
1416
+ type: "filename" /* FILENAME */,
1417
+ isActive: prefix.length > 0
1418
+ };
1419
+ return [];
1439
1420
  }
1440
1421
  /**
1441
1422
  * Start omni completion - context-aware completion
@@ -1499,17 +1480,10 @@ var CompletionManager = class _CompletionManager {
1499
1480
  }
1500
1481
  /**
1501
1482
  * Get file type information
1483
+ * Note: Stub implementation for browser compatibility
1502
1484
  */
1503
1485
  getFileTypeInfo(filepath) {
1504
- try {
1505
- const stat = fs.statSync(filepath);
1506
- if (stat.isDirectory()) return "directory";
1507
- if (stat.isFile()) return "file";
1508
- if (stat.isSymbolicLink()) return "symlink";
1509
- return "unknown";
1510
- } catch {
1511
- return "unknown";
1512
- }
1486
+ return "unknown";
1513
1487
  }
1514
1488
  /**
1515
1489
  * Get completion text to insert (without prefix)
@@ -1547,9 +1521,342 @@ var CompletionManager = class _CompletionManager {
1547
1521
  }
1548
1522
  };
1549
1523
 
1524
+ // src/core/config-manager.ts
1525
+ var ConfigManager = class _ConfigManager {
1526
+ config;
1527
+ listeners = /* @__PURE__ */ new Map();
1528
+ constructor(initialConfig) {
1529
+ this.config = {
1530
+ // Display
1531
+ number: false,
1532
+ relativenumber: false,
1533
+ wrap: true,
1534
+ cursorline: false,
1535
+ // Indentation
1536
+ tabstop: 4,
1537
+ shiftwidth: 2,
1538
+ expandtab: true,
1539
+ autoindent: true,
1540
+ smartindent: false,
1541
+ // Search
1542
+ ignorecase: false,
1543
+ smartcase: false,
1544
+ hlsearch: true,
1545
+ incsearch: true,
1546
+ wrapscan: true,
1547
+ // Motion behavior
1548
+ whichwrap: "h,l",
1549
+ // vim-sim default: h,l wrap (Vim default: "b,s")
1550
+ startofline: true,
1551
+ // Vim default: true
1552
+ virtualedit: "",
1553
+ // Vim default: ""
1554
+ // Editing
1555
+ undolevels: 1e3,
1556
+ clipboard: "",
1557
+ ...initialConfig
1558
+ };
1559
+ }
1560
+ /**
1561
+ * Get a config value
1562
+ */
1563
+ get(key) {
1564
+ return this.config[key];
1565
+ }
1566
+ /**
1567
+ * Set a config value
1568
+ */
1569
+ set(key, value) {
1570
+ const oldValue = this.config[key];
1571
+ if (oldValue === value) return;
1572
+ this.config[key] = value;
1573
+ this.notifyListeners(key, value);
1574
+ }
1575
+ /**
1576
+ * Set multiple config values at once
1577
+ */
1578
+ setMultiple(updates) {
1579
+ for (const [key, value] of Object.entries(updates)) {
1580
+ this.set(key, value);
1581
+ }
1582
+ }
1583
+ /**
1584
+ * Get all config values
1585
+ */
1586
+ getAll() {
1587
+ return { ...this.config };
1588
+ }
1589
+ /**
1590
+ * Reset to default configuration
1591
+ */
1592
+ reset() {
1593
+ const defaults = new _ConfigManager().getAll();
1594
+ this.setMultiple(defaults);
1595
+ }
1596
+ /**
1597
+ * Toggle a boolean config option
1598
+ */
1599
+ toggle(key) {
1600
+ const value = this.config[key];
1601
+ if (typeof value === "boolean") {
1602
+ this.set(key, !value);
1603
+ }
1604
+ }
1605
+ /**
1606
+ * Subscribe to config changes
1607
+ * @param key - Config key to watch, or '*' for all changes
1608
+ * @param callback - Function to call when config changes
1609
+ * @returns Unsubscribe function
1610
+ */
1611
+ onChange(key, callback) {
1612
+ if (!this.listeners.has(key)) {
1613
+ this.listeners.set(key, /* @__PURE__ */ new Set());
1614
+ }
1615
+ this.listeners.get(key).add(callback);
1616
+ return () => {
1617
+ this.listeners.get(key)?.delete(callback);
1618
+ };
1619
+ }
1620
+ /**
1621
+ * Notify all listeners of a config change
1622
+ */
1623
+ notifyListeners(key, value) {
1624
+ this.listeners.get(key)?.forEach((callback) => callback(key, value));
1625
+ this.listeners.get("*")?.forEach((callback) => callback(key, value));
1626
+ }
1627
+ /**
1628
+ * Parse a Vim set command string (e.g., "number", "nonumber", "tabstop=4")
1629
+ */
1630
+ parseSetCommand(command) {
1631
+ const noMatch = command.match(/^no(\w+)$/);
1632
+ if (noMatch) {
1633
+ const key2 = noMatch[1];
1634
+ if (key2 in this.config && typeof this.config[key2] === "boolean") {
1635
+ this.set(key2, false);
1636
+ return true;
1637
+ }
1638
+ return false;
1639
+ }
1640
+ const valueMatch = command.match(/^(\w+)=(.+)$/);
1641
+ if (valueMatch) {
1642
+ const key2 = valueMatch[1];
1643
+ const value = valueMatch[2];
1644
+ if (!(key2 in this.config)) return false;
1645
+ const currentValue = this.config[key2];
1646
+ if (typeof currentValue === "number") {
1647
+ const numValue = parseInt(value, 10);
1648
+ if (!isNaN(numValue)) {
1649
+ this.set(key2, numValue);
1650
+ return true;
1651
+ }
1652
+ } else if (typeof currentValue === "string") {
1653
+ this.set(key2, value);
1654
+ return true;
1655
+ }
1656
+ return false;
1657
+ }
1658
+ const key = command;
1659
+ if (key in this.config && typeof this.config[key] === "boolean") {
1660
+ this.set(key, true);
1661
+ return true;
1662
+ }
1663
+ return false;
1664
+ }
1665
+ /**
1666
+ * Get a formatted string representation of a config value
1667
+ */
1668
+ formatValue(key) {
1669
+ const value = this.config[key];
1670
+ if (typeof value === "boolean") {
1671
+ return value ? key : `no${key}`;
1672
+ }
1673
+ return `${key}=${value}`;
1674
+ }
1675
+ /**
1676
+ * Get metadata about a config option
1677
+ */
1678
+ getMetadata(key) {
1679
+ return CONFIG_METADATA[key];
1680
+ }
1681
+ /**
1682
+ * Get all options that differ from vanilla Vim
1683
+ */
1684
+ getDifferencesFromVim() {
1685
+ return Object.entries(CONFIG_METADATA).filter(([_, meta]) => meta.differsFromVim).map(([key, _]) => key);
1686
+ }
1687
+ /**
1688
+ * Get options by category
1689
+ */
1690
+ getByCategory(category) {
1691
+ return Object.entries(CONFIG_METADATA).filter(([_, meta]) => meta.category === category).map(([key, _]) => key);
1692
+ }
1693
+ };
1694
+ var CONFIG_METADATA = {
1695
+ // Display options
1696
+ number: {
1697
+ description: "Show line numbers in the left margin",
1698
+ vimDefault: false,
1699
+ vimSimDefault: false,
1700
+ category: "display",
1701
+ type: "boolean",
1702
+ differsFromVim: false
1703
+ },
1704
+ relativenumber: {
1705
+ description: "Show line numbers relative to cursor position",
1706
+ vimDefault: false,
1707
+ vimSimDefault: false,
1708
+ category: "display",
1709
+ type: "boolean",
1710
+ differsFromVim: false
1711
+ },
1712
+ wrap: {
1713
+ description: "Wrap long lines to fit in window",
1714
+ vimDefault: true,
1715
+ vimSimDefault: true,
1716
+ category: "display",
1717
+ type: "boolean",
1718
+ differsFromVim: false
1719
+ },
1720
+ cursorline: {
1721
+ description: "Highlight the screen line of the cursor",
1722
+ vimDefault: false,
1723
+ vimSimDefault: false,
1724
+ category: "display",
1725
+ type: "boolean",
1726
+ differsFromVim: false
1727
+ },
1728
+ // Indentation options
1729
+ tabstop: {
1730
+ description: "Number of spaces that a <Tab> in the file counts for",
1731
+ vimDefault: 8,
1732
+ vimSimDefault: 4,
1733
+ category: "indentation",
1734
+ type: "number",
1735
+ differsFromVim: true
1736
+ },
1737
+ shiftwidth: {
1738
+ description: "Number of spaces to use for each step of (auto)indent",
1739
+ vimDefault: 8,
1740
+ vimSimDefault: 2,
1741
+ category: "indentation",
1742
+ type: "number",
1743
+ differsFromVim: true
1744
+ },
1745
+ expandtab: {
1746
+ description: "Use spaces instead of tabs when pressing <Tab>",
1747
+ vimDefault: false,
1748
+ vimSimDefault: true,
1749
+ category: "indentation",
1750
+ type: "boolean",
1751
+ differsFromVim: true
1752
+ },
1753
+ autoindent: {
1754
+ description: "Copy indent from current line when starting a new line",
1755
+ vimDefault: false,
1756
+ vimSimDefault: true,
1757
+ category: "indentation",
1758
+ type: "boolean",
1759
+ differsFromVim: true
1760
+ },
1761
+ smartindent: {
1762
+ description: "Do smart autoindenting when starting a new line",
1763
+ vimDefault: false,
1764
+ vimSimDefault: false,
1765
+ category: "indentation",
1766
+ type: "boolean",
1767
+ differsFromVim: false
1768
+ },
1769
+ // Search options
1770
+ ignorecase: {
1771
+ description: "Ignore case in search patterns",
1772
+ vimDefault: false,
1773
+ vimSimDefault: false,
1774
+ category: "search",
1775
+ type: "boolean",
1776
+ differsFromVim: false
1777
+ },
1778
+ smartcase: {
1779
+ description: "Override ignorecase if search pattern contains uppercase",
1780
+ vimDefault: false,
1781
+ vimSimDefault: false,
1782
+ category: "search",
1783
+ type: "boolean",
1784
+ differsFromVim: false
1785
+ },
1786
+ hlsearch: {
1787
+ description: "Highlight all matches for the search pattern",
1788
+ vimDefault: false,
1789
+ vimSimDefault: true,
1790
+ category: "search",
1791
+ type: "boolean",
1792
+ differsFromVim: true
1793
+ },
1794
+ incsearch: {
1795
+ description: "Show match for partly typed search pattern",
1796
+ vimDefault: false,
1797
+ vimSimDefault: true,
1798
+ category: "search",
1799
+ type: "boolean",
1800
+ differsFromVim: true
1801
+ },
1802
+ wrapscan: {
1803
+ description: "Searches wrap around the end of the file",
1804
+ vimDefault: true,
1805
+ vimSimDefault: true,
1806
+ category: "search",
1807
+ type: "boolean",
1808
+ differsFromVim: false
1809
+ },
1810
+ // Motion behavior options
1811
+ whichwrap: {
1812
+ description: "Keys that wrap to prev/next line (b=<BS>, s=<Space>, h=h, l=l, <=<Left>, >=<Right>, [=<Left> in insert, ]=<Right> in insert)",
1813
+ vimDefault: "b,s",
1814
+ vimSimDefault: "h,l",
1815
+ category: "motion",
1816
+ type: "string",
1817
+ differsFromVim: true,
1818
+ validValues: ["b", "s", "h", "l", "<", ">", "[", "]", "~"]
1819
+ },
1820
+ startofline: {
1821
+ description: "Move cursor to first non-blank when changing lines with G, gg, d, etc.",
1822
+ vimDefault: true,
1823
+ vimSimDefault: true,
1824
+ category: "motion",
1825
+ type: "boolean",
1826
+ differsFromVim: false
1827
+ },
1828
+ virtualedit: {
1829
+ description: "Allow cursor positioning where there is no character (block, insert, all, onemore)",
1830
+ vimDefault: "",
1831
+ vimSimDefault: "",
1832
+ category: "motion",
1833
+ type: "string",
1834
+ differsFromVim: false,
1835
+ validValues: ["block", "insert", "all", "onemore", ""]
1836
+ },
1837
+ // Editing options
1838
+ undolevels: {
1839
+ description: "Maximum number of changes that can be undone",
1840
+ vimDefault: 1e3,
1841
+ vimSimDefault: 1e3,
1842
+ category: "editing",
1843
+ type: "number",
1844
+ differsFromVim: false
1845
+ },
1846
+ clipboard: {
1847
+ description: "Use system clipboard for unnamed register",
1848
+ vimDefault: "",
1849
+ vimSimDefault: "",
1850
+ category: "editing",
1851
+ type: "string",
1852
+ differsFromVim: false,
1853
+ validValues: ["unnamed", "unnamedplus", ""]
1854
+ }
1855
+ };
1856
+
1550
1857
  // src/core/state.ts
1551
1858
  var State = class {
1552
- constructor(buffer, cursor, selection, mode, commandLine = "", desiredColumn = null, viewport = new VimViewport(), visualAnchor = null, lastVisualSelection = null, recordingRegister = null, lastMacroRegister = null, foldManager = new FoldManager(), markManager = new MarkManager(), jumpListManager = new JumpListManager(), fileSystem = new FileSystemManager(), windowManager = new WindowManager(), lastSearch = null, spellChecker = new SpellChecker(), completionManager = new CompletionManager(), undoTree = null, lastCommand = null) {
1859
+ constructor(buffer, cursor, selection, mode, commandLine = "", desiredColumn = null, viewport = new VimViewport(), visualAnchor = null, lastVisualSelection = null, recordingRegister = null, lastMacroRegister = null, foldManager = new FoldManager(), markManager = new MarkManager(), jumpListManager = new JumpListManager(), fileSystem = new FileSystemManager(), windowManager = new WindowManager(), lastSearch = null, spellChecker = new SpellChecker(), completionManager = new CompletionManager(), undoTree = null, lastCommand = null, configManager = null) {
1553
1860
  this.buffer = buffer;
1554
1861
  this.cursor = cursor;
1555
1862
  this.selection = selection;
@@ -1571,6 +1878,7 @@ var State = class {
1571
1878
  this.completionManager = completionManager;
1572
1879
  this.undoTree = undoTree;
1573
1880
  this.lastCommand = lastCommand;
1881
+ this.configManager = configManager;
1574
1882
  }
1575
1883
  };
1576
1884
  function createEmptyState() {
@@ -1596,8 +1904,10 @@ function createEmptyState() {
1596
1904
  new CompletionManager(),
1597
1905
  null,
1598
1906
  // undoTree will be initialized separately
1599
- null
1907
+ null,
1600
1908
  // lastCommand
1909
+ new ConfigManager()
1910
+ // configManager with default settings
1601
1911
  );
1602
1912
  return state;
1603
1913
  }
@@ -1737,6 +2047,29 @@ function handleInsertArrowDown(state) {
1737
2047
  }
1738
2048
  return state;
1739
2049
  }
2050
+ function handleInsertDelete(state) {
2051
+ const lines = state.buffer.content.split("\n");
2052
+ const currentLine2 = lines[state.cursor.line] ?? "";
2053
+ if (state.cursor.column < currentLine2.length) {
2054
+ const newLine = currentLine2.slice(0, state.cursor.column) + currentLine2.slice(state.cursor.column + 1);
2055
+ lines[state.cursor.line] = newLine;
2056
+ return {
2057
+ ...state,
2058
+ buffer: new Buffer(lines.join("\n")),
2059
+ desiredColumn: null
2060
+ };
2061
+ } else if (state.cursor.line < lines.length - 1) {
2062
+ const nextLine = lines[state.cursor.line + 1] ?? "";
2063
+ lines[state.cursor.line] = currentLine2 + nextLine;
2064
+ lines.splice(state.cursor.line + 1, 1);
2065
+ return {
2066
+ ...state,
2067
+ buffer: new Buffer(lines.join("\n")),
2068
+ desiredColumn: null
2069
+ };
2070
+ }
2071
+ return state;
2072
+ }
1740
2073
  function handleInsertCharacter(state, char) {
1741
2074
  const lines = state.buffer.content.split("\n");
1742
2075
  const currentLine2 = lines[state.cursor.line] ?? "";
@@ -2139,7 +2472,17 @@ function withBufferContent(state, content) {
2139
2472
  state.visualAnchor,
2140
2473
  state.lastVisualSelection,
2141
2474
  state.recordingRegister,
2142
- state.lastMacroRegister
2475
+ state.lastMacroRegister,
2476
+ state.foldManager,
2477
+ state.markManager,
2478
+ state.jumpListManager,
2479
+ state.fileSystem,
2480
+ state.windowManager,
2481
+ state.lastSearch,
2482
+ state.spellChecker,
2483
+ state.completionManager,
2484
+ state.undoTree,
2485
+ state.lastCommand
2143
2486
  );
2144
2487
  }
2145
2488
  function withCursor(state, cursor, preserveDesiredColumn = true) {
@@ -2154,7 +2497,17 @@ function withCursor(state, cursor, preserveDesiredColumn = true) {
2154
2497
  state.visualAnchor,
2155
2498
  state.lastVisualSelection,
2156
2499
  state.recordingRegister,
2157
- state.lastMacroRegister
2500
+ state.lastMacroRegister,
2501
+ state.foldManager,
2502
+ state.markManager,
2503
+ state.jumpListManager,
2504
+ state.fileSystem,
2505
+ state.windowManager,
2506
+ state.lastSearch,
2507
+ state.spellChecker,
2508
+ state.completionManager,
2509
+ state.undoTree,
2510
+ state.lastCommand
2158
2511
  );
2159
2512
  }
2160
2513
  function withCursorVertical(state, cursor, targetColumn) {
@@ -2166,11 +2519,22 @@ function withCursorVertical(state, cursor, targetColumn) {
2166
2519
  state.mode,
2167
2520
  state.commandLine,
2168
2521
  desiredCol,
2522
+ // Maintain desiredColumn for vertical movements
2169
2523
  state.viewport,
2170
2524
  state.visualAnchor,
2171
2525
  state.lastVisualSelection,
2172
2526
  state.recordingRegister,
2173
- state.lastMacroRegister
2527
+ state.lastMacroRegister,
2528
+ state.foldManager,
2529
+ state.markManager,
2530
+ state.jumpListManager,
2531
+ state.fileSystem,
2532
+ state.windowManager,
2533
+ state.lastSearch,
2534
+ state.spellChecker,
2535
+ state.completionManager,
2536
+ state.undoTree,
2537
+ state.lastCommand
2174
2538
  );
2175
2539
  }
2176
2540
  function withCursorHorizontal(state, cursor) {
@@ -2181,11 +2545,22 @@ function withCursorHorizontal(state, cursor) {
2181
2545
  state.mode,
2182
2546
  state.commandLine,
2183
2547
  null,
2548
+ // Reset desiredColumn for horizontal movements
2184
2549
  state.viewport,
2185
2550
  state.visualAnchor,
2186
2551
  state.lastVisualSelection,
2187
2552
  state.recordingRegister,
2188
- state.lastMacroRegister
2553
+ state.lastMacroRegister,
2554
+ state.foldManager,
2555
+ state.markManager,
2556
+ state.jumpListManager,
2557
+ state.fileSystem,
2558
+ state.windowManager,
2559
+ state.lastSearch,
2560
+ state.spellChecker,
2561
+ state.completionManager,
2562
+ state.undoTree,
2563
+ state.lastCommand
2189
2564
  );
2190
2565
  }
2191
2566
  function withViewport(state, topLine) {
@@ -2471,18 +2846,24 @@ var B = class extends Motion {
2471
2846
  };
2472
2847
 
2473
2848
  // src/motions/basic.ts
2849
+ function canWrap(state, key) {
2850
+ const whichwrap = state.configManager?.get("whichwrap") ?? "h,l";
2851
+ const allowed = whichwrap.split(",").map((s) => s.trim());
2852
+ return allowed.includes(key);
2853
+ }
2474
2854
  var h = class extends Motion {
2475
2855
  key = "h";
2476
2856
  execute(state, context) {
2477
2857
  let count = getCount(context);
2478
2858
  let line = state.cursor.line;
2479
2859
  let col = state.cursor.column;
2860
+ const allowWrap = canWrap(state, "h");
2480
2861
  while (count > 0) {
2481
2862
  if (col > 0) {
2482
2863
  const moveAmount = Math.min(col, count);
2483
2864
  col -= moveAmount;
2484
2865
  count -= moveAmount;
2485
- } else if (line > 0) {
2866
+ } else if (line > 0 && allowWrap) {
2486
2867
  line--;
2487
2868
  const prevLine = getLine(state.buffer, line);
2488
2869
  col = Math.max(0, prevLine.length - 1);
@@ -2494,11 +2875,53 @@ var h = class extends Motion {
2494
2875
  return withCursorHorizontal(state, new Cursor(line, col));
2495
2876
  }
2496
2877
  };
2497
- var Backspace = class extends h {
2878
+ var Backspace = class extends Motion {
2498
2879
  key = "Backspace";
2880
+ execute(state, context) {
2881
+ let count = getCount(context);
2882
+ let line = state.cursor.line;
2883
+ let col = state.cursor.column;
2884
+ const allowWrap = canWrap(state, "b");
2885
+ while (count > 0) {
2886
+ if (col > 0) {
2887
+ const moveAmount = Math.min(col, count);
2888
+ col -= moveAmount;
2889
+ count -= moveAmount;
2890
+ } else if (line > 0 && allowWrap) {
2891
+ line--;
2892
+ const prevLine = getLine(state.buffer, line);
2893
+ col = Math.max(0, prevLine.length - 1);
2894
+ count--;
2895
+ } else {
2896
+ break;
2897
+ }
2898
+ }
2899
+ return withCursorHorizontal(state, new Cursor(line, col));
2900
+ }
2499
2901
  };
2500
- var ArrowLeft = class extends h {
2902
+ var ArrowLeft = class extends Motion {
2501
2903
  key = "ArrowLeft";
2904
+ execute(state, context) {
2905
+ let count = getCount(context);
2906
+ let line = state.cursor.line;
2907
+ let col = state.cursor.column;
2908
+ const allowWrap = canWrap(state, "<");
2909
+ while (count > 0) {
2910
+ if (col > 0) {
2911
+ const moveAmount = Math.min(col, count);
2912
+ col -= moveAmount;
2913
+ count -= moveAmount;
2914
+ } else if (line > 0 && allowWrap) {
2915
+ line--;
2916
+ const prevLine = getLine(state.buffer, line);
2917
+ col = Math.max(0, prevLine.length - 1);
2918
+ count--;
2919
+ } else {
2920
+ break;
2921
+ }
2922
+ }
2923
+ return withCursorHorizontal(state, new Cursor(line, col));
2924
+ }
2502
2925
  };
2503
2926
  var l = class extends Motion {
2504
2927
  key = "l";
@@ -2506,7 +2929,7 @@ var l = class extends Motion {
2506
2929
  let count = getCount(context);
2507
2930
  let line = state.cursor.line;
2508
2931
  let col = state.cursor.column;
2509
- const totalLines = lastLine(state) + 1;
2932
+ const allowWrap = canWrap(state, "l");
2510
2933
  while (count > 0) {
2511
2934
  const currentLine2 = getLine(state.buffer, line);
2512
2935
  const maxCol = Math.max(0, currentLine2.length - 1);
@@ -2514,7 +2937,7 @@ var l = class extends Motion {
2514
2937
  const moveAmount = Math.min(maxCol - col, count);
2515
2938
  col += moveAmount;
2516
2939
  count -= moveAmount;
2517
- } else if (line < lastLine(state)) {
2940
+ } else if (line < lastLine(state) && allowWrap) {
2518
2941
  line++;
2519
2942
  col = 0;
2520
2943
  count--;
@@ -2525,8 +2948,30 @@ var l = class extends Motion {
2525
2948
  return withCursorHorizontal(state, new Cursor(line, col));
2526
2949
  }
2527
2950
  };
2528
- var ArrowRight = class extends l {
2951
+ var ArrowRight = class extends Motion {
2529
2952
  key = "ArrowRight";
2953
+ execute(state, context) {
2954
+ let count = getCount(context);
2955
+ let line = state.cursor.line;
2956
+ let col = state.cursor.column;
2957
+ const allowWrap = canWrap(state, ">");
2958
+ while (count > 0) {
2959
+ const currentLine2 = getLine(state.buffer, line);
2960
+ const maxCol = Math.max(0, currentLine2.length - 1);
2961
+ if (col < maxCol) {
2962
+ const moveAmount = Math.min(maxCol - col, count);
2963
+ col += moveAmount;
2964
+ count -= moveAmount;
2965
+ } else if (line < lastLine(state) && allowWrap) {
2966
+ line++;
2967
+ col = 0;
2968
+ count--;
2969
+ } else {
2970
+ break;
2971
+ }
2972
+ }
2973
+ return withCursorHorizontal(state, new Cursor(line, col));
2974
+ }
2530
2975
  };
2531
2976
  var j = class extends Motion {
2532
2977
  key = "j";
@@ -2838,151 +3283,6 @@ var IndentationManager = class {
2838
3283
  }
2839
3284
  };
2840
3285
 
2841
- // src/core/config-manager.ts
2842
- var ConfigManager = class _ConfigManager {
2843
- config;
2844
- listeners = /* @__PURE__ */ new Map();
2845
- constructor(initialConfig) {
2846
- this.config = {
2847
- // Display
2848
- number: false,
2849
- relativenumber: false,
2850
- wrap: true,
2851
- cursorline: false,
2852
- // Indentation
2853
- tabstop: 4,
2854
- shiftwidth: 2,
2855
- expandtab: true,
2856
- autoindent: true,
2857
- smartindent: false,
2858
- // Search
2859
- ignorecase: false,
2860
- smartcase: false,
2861
- hlsearch: true,
2862
- incsearch: true,
2863
- // Editing
2864
- undolevels: 1e3,
2865
- clipboard: "",
2866
- ...initialConfig
2867
- };
2868
- }
2869
- /**
2870
- * Get a config value
2871
- */
2872
- get(key) {
2873
- return this.config[key];
2874
- }
2875
- /**
2876
- * Set a config value
2877
- */
2878
- set(key, value) {
2879
- const oldValue = this.config[key];
2880
- if (oldValue === value) return;
2881
- this.config[key] = value;
2882
- this.notifyListeners(key, value);
2883
- }
2884
- /**
2885
- * Set multiple config values at once
2886
- */
2887
- setMultiple(updates) {
2888
- for (const [key, value] of Object.entries(updates)) {
2889
- this.set(key, value);
2890
- }
2891
- }
2892
- /**
2893
- * Get all config values
2894
- */
2895
- getAll() {
2896
- return { ...this.config };
2897
- }
2898
- /**
2899
- * Reset to default configuration
2900
- */
2901
- reset() {
2902
- const defaults = new _ConfigManager().getAll();
2903
- this.setMultiple(defaults);
2904
- }
2905
- /**
2906
- * Toggle a boolean config option
2907
- */
2908
- toggle(key) {
2909
- const value = this.config[key];
2910
- if (typeof value === "boolean") {
2911
- this.set(key, !value);
2912
- }
2913
- }
2914
- /**
2915
- * Subscribe to config changes
2916
- * @param key - Config key to watch, or '*' for all changes
2917
- * @param callback - Function to call when config changes
2918
- * @returns Unsubscribe function
2919
- */
2920
- onChange(key, callback) {
2921
- if (!this.listeners.has(key)) {
2922
- this.listeners.set(key, /* @__PURE__ */ new Set());
2923
- }
2924
- this.listeners.get(key).add(callback);
2925
- return () => {
2926
- this.listeners.get(key)?.delete(callback);
2927
- };
2928
- }
2929
- /**
2930
- * Notify all listeners of a config change
2931
- */
2932
- notifyListeners(key, value) {
2933
- this.listeners.get(key)?.forEach((callback) => callback(key, value));
2934
- this.listeners.get("*")?.forEach((callback) => callback(key, value));
2935
- }
2936
- /**
2937
- * Parse a Vim set command string (e.g., "number", "nonumber", "tabstop=4")
2938
- */
2939
- parseSetCommand(command) {
2940
- const noMatch = command.match(/^no(\w+)$/);
2941
- if (noMatch) {
2942
- const key2 = noMatch[1];
2943
- if (key2 in this.config && typeof this.config[key2] === "boolean") {
2944
- this.set(key2, false);
2945
- return true;
2946
- }
2947
- return false;
2948
- }
2949
- const valueMatch = command.match(/^(\w+)=(.+)$/);
2950
- if (valueMatch) {
2951
- const key2 = valueMatch[1];
2952
- const value = valueMatch[2];
2953
- if (!(key2 in this.config)) return false;
2954
- const currentValue = this.config[key2];
2955
- if (typeof currentValue === "number") {
2956
- const numValue = parseInt(value, 10);
2957
- if (!isNaN(numValue)) {
2958
- this.set(key2, numValue);
2959
- return true;
2960
- }
2961
- } else if (typeof currentValue === "string") {
2962
- this.set(key2, value);
2963
- return true;
2964
- }
2965
- return false;
2966
- }
2967
- const key = command;
2968
- if (key in this.config && typeof this.config[key] === "boolean") {
2969
- this.set(key, true);
2970
- return true;
2971
- }
2972
- return false;
2973
- }
2974
- /**
2975
- * Get a formatted string representation of a config value
2976
- */
2977
- formatValue(key) {
2978
- const value = this.config[key];
2979
- if (typeof value === "boolean") {
2980
- return value ? key : `no${key}`;
2981
- }
2982
- return `${key}=${value}`;
2983
- }
2984
- };
2985
-
2986
3286
  // src/core/mode-transition.ts
2987
3287
  var ModeTransition = class extends Command {
2988
3288
  // Optional hook to modify state before mode transition
@@ -3146,11 +3446,23 @@ var dd = class extends Command {
3146
3446
  };
3147
3447
  var Delete = class extends Operator {
3148
3448
  key = "Delete";
3449
+ // Override Operator defaults - Delete doesn't accept motions/text objects
3450
+ acceptsMotion = false;
3451
+ acceptsTextObject = false;
3149
3452
  execute(state, context) {
3150
3453
  if (state.selection) {
3151
3454
  return deleteRangeWithRegister(state, state.selection, context, false);
3152
3455
  }
3153
- return deleteRangeWithRegister(state, { start: state.cursor, end: new Cursor(state.cursor.line, state.cursor.column + 1) }, context, false);
3456
+ const count = context.count ?? 1;
3457
+ return deleteRangeWithRegister(
3458
+ state,
3459
+ {
3460
+ start: state.cursor,
3461
+ end: new Cursor(state.cursor.line, state.cursor.column + count)
3462
+ },
3463
+ context,
3464
+ false
3465
+ );
3154
3466
  }
3155
3467
  };
3156
3468
 
@@ -3841,7 +4153,7 @@ var gE = class extends Motion {
3841
4153
  return repeatCursorMotion(state, context, findPrevWORDEnd);
3842
4154
  }
3843
4155
  };
3844
- var LastNonBlank = class extends Motion {
4156
+ var g_ = class extends Motion {
3845
4157
  key = "g_";
3846
4158
  inclusive = true;
3847
4159
  // g_ is inclusive
@@ -3855,7 +4167,7 @@ var LastNonBlank = class extends Motion {
3855
4167
  return withCursorHorizontal(state, new Cursor(state.cursor.line, col));
3856
4168
  }
3857
4169
  };
3858
- var MiddleOfScreenLine = class extends Motion {
4170
+ var gm = class extends Motion {
3859
4171
  key = "gm";
3860
4172
  execute(state) {
3861
4173
  const line = getLine(state.buffer, state.cursor.line);
@@ -6234,18 +6546,11 @@ var Session = class _Session {
6234
6546
  }
6235
6547
  this.keyBuffer.push(key);
6236
6548
  const input = this.keyBuffer.join("");
6237
- console.log("[DEBUG] handleKey - input:", input, "keyBuffer:", this.keyBuffer);
6238
6549
  const result = this.match(input, this.keyBuffer);
6239
- console.log("[DEBUG] handleKey - match result:", result ? result === "partial" ? "partial" : `complete: ${result.command.key}` : "null");
6240
6550
  if (result && result !== "partial" && result.isComplete) {
6241
6551
  const stateBefore = this.state;
6242
6552
  result.context.registerManager = this.registerManager;
6243
6553
  result.context.indentationManager = this.indentationManager;
6244
- console.log("[DEBUG] Executing command:", result.command.key, "with context:", {
6245
- count: result.context.count,
6246
- motion: result.context.motion?.key,
6247
- char: result.context.char
6248
- });
6249
6554
  this.state = result.command.execute(this.state, result.context);
6250
6555
  if ((stateBefore.mode === 2 /* VISUAL */ || stateBefore.mode === 3 /* VISUAL_LINE */ || stateBefore.mode === 4 /* VISUAL_BLOCK */) && this.state.mode === stateBefore.mode && // Still in same visual mode
6251
6556
  stateBefore.visualAnchor && (this.state.cursor.line !== stateBefore.cursor.line || this.state.cursor.column !== stateBefore.cursor.column)) {
@@ -6332,6 +6637,8 @@ var Session = class _Session {
6332
6637
  this.state = handleInsertEnter(this.state, this.indentationManager);
6333
6638
  } else if (key === "Space") {
6334
6639
  this.state = handleInsertSpace(this.state);
6640
+ } else if (key === "Delete") {
6641
+ this.state = handleInsertDelete(this.state);
6335
6642
  } else if (key === "ArrowLeft") {
6336
6643
  this.state = handleInsertArrowLeft(this.state);
6337
6644
  } else if (key === "ArrowRight") {
@@ -6648,8 +6955,8 @@ var Session = class _Session {
6648
6955
  addMotionAllModes(new MatchPercent());
6649
6956
  addMotionAllModes(new ge());
6650
6957
  addMotionAllModes(new gE());
6651
- addMotionAllModes(new LastNonBlank());
6652
- addMotionAllModes(new MiddleOfScreenLine());
6958
+ addMotionAllModes(new g_());
6959
+ addMotionAllModes(new gm());
6653
6960
  addMotionAllModes(new MiddleOfLine());
6654
6961
  addMotionAllModes(new Column());
6655
6962
  addMotionAllModes(new Star());
@@ -6845,6 +7152,7 @@ var PatternRegistry = class {
6845
7152
  };
6846
7153
  export {
6847
7154
  Buffer,
7155
+ CONFIG_METADATA,
6848
7156
  Change,
6849
7157
  Command,
6850
7158
  ConfigManager,