vim-sim 1.0.2 → 1.0.3
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/README.md +30 -30
- package/dist/index.d.ts +109 -75
- package/dist/index.js +465 -168
- package/dist/index.js.map +1 -1
- package/docs/config-system-improvements.md +221 -0
- package/docs/vim-compatibility.md +246 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1547,9 +1547,342 @@ var CompletionManager = class _CompletionManager {
|
|
|
1547
1547
|
}
|
|
1548
1548
|
};
|
|
1549
1549
|
|
|
1550
|
+
// src/core/config-manager.ts
|
|
1551
|
+
var ConfigManager = class _ConfigManager {
|
|
1552
|
+
config;
|
|
1553
|
+
listeners = /* @__PURE__ */ new Map();
|
|
1554
|
+
constructor(initialConfig) {
|
|
1555
|
+
this.config = {
|
|
1556
|
+
// Display
|
|
1557
|
+
number: false,
|
|
1558
|
+
relativenumber: false,
|
|
1559
|
+
wrap: true,
|
|
1560
|
+
cursorline: false,
|
|
1561
|
+
// Indentation
|
|
1562
|
+
tabstop: 4,
|
|
1563
|
+
shiftwidth: 2,
|
|
1564
|
+
expandtab: true,
|
|
1565
|
+
autoindent: true,
|
|
1566
|
+
smartindent: false,
|
|
1567
|
+
// Search
|
|
1568
|
+
ignorecase: false,
|
|
1569
|
+
smartcase: false,
|
|
1570
|
+
hlsearch: true,
|
|
1571
|
+
incsearch: true,
|
|
1572
|
+
wrapscan: true,
|
|
1573
|
+
// Motion behavior
|
|
1574
|
+
whichwrap: "h,l",
|
|
1575
|
+
// vim-sim default: h,l wrap (Vim default: "b,s")
|
|
1576
|
+
startofline: true,
|
|
1577
|
+
// Vim default: true
|
|
1578
|
+
virtualedit: "",
|
|
1579
|
+
// Vim default: ""
|
|
1580
|
+
// Editing
|
|
1581
|
+
undolevels: 1e3,
|
|
1582
|
+
clipboard: "",
|
|
1583
|
+
...initialConfig
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
/**
|
|
1587
|
+
* Get a config value
|
|
1588
|
+
*/
|
|
1589
|
+
get(key) {
|
|
1590
|
+
return this.config[key];
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* Set a config value
|
|
1594
|
+
*/
|
|
1595
|
+
set(key, value) {
|
|
1596
|
+
const oldValue = this.config[key];
|
|
1597
|
+
if (oldValue === value) return;
|
|
1598
|
+
this.config[key] = value;
|
|
1599
|
+
this.notifyListeners(key, value);
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Set multiple config values at once
|
|
1603
|
+
*/
|
|
1604
|
+
setMultiple(updates) {
|
|
1605
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
1606
|
+
this.set(key, value);
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Get all config values
|
|
1611
|
+
*/
|
|
1612
|
+
getAll() {
|
|
1613
|
+
return { ...this.config };
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Reset to default configuration
|
|
1617
|
+
*/
|
|
1618
|
+
reset() {
|
|
1619
|
+
const defaults = new _ConfigManager().getAll();
|
|
1620
|
+
this.setMultiple(defaults);
|
|
1621
|
+
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Toggle a boolean config option
|
|
1624
|
+
*/
|
|
1625
|
+
toggle(key) {
|
|
1626
|
+
const value = this.config[key];
|
|
1627
|
+
if (typeof value === "boolean") {
|
|
1628
|
+
this.set(key, !value);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Subscribe to config changes
|
|
1633
|
+
* @param key - Config key to watch, or '*' for all changes
|
|
1634
|
+
* @param callback - Function to call when config changes
|
|
1635
|
+
* @returns Unsubscribe function
|
|
1636
|
+
*/
|
|
1637
|
+
onChange(key, callback) {
|
|
1638
|
+
if (!this.listeners.has(key)) {
|
|
1639
|
+
this.listeners.set(key, /* @__PURE__ */ new Set());
|
|
1640
|
+
}
|
|
1641
|
+
this.listeners.get(key).add(callback);
|
|
1642
|
+
return () => {
|
|
1643
|
+
this.listeners.get(key)?.delete(callback);
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
/**
|
|
1647
|
+
* Notify all listeners of a config change
|
|
1648
|
+
*/
|
|
1649
|
+
notifyListeners(key, value) {
|
|
1650
|
+
this.listeners.get(key)?.forEach((callback) => callback(key, value));
|
|
1651
|
+
this.listeners.get("*")?.forEach((callback) => callback(key, value));
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Parse a Vim set command string (e.g., "number", "nonumber", "tabstop=4")
|
|
1655
|
+
*/
|
|
1656
|
+
parseSetCommand(command) {
|
|
1657
|
+
const noMatch = command.match(/^no(\w+)$/);
|
|
1658
|
+
if (noMatch) {
|
|
1659
|
+
const key2 = noMatch[1];
|
|
1660
|
+
if (key2 in this.config && typeof this.config[key2] === "boolean") {
|
|
1661
|
+
this.set(key2, false);
|
|
1662
|
+
return true;
|
|
1663
|
+
}
|
|
1664
|
+
return false;
|
|
1665
|
+
}
|
|
1666
|
+
const valueMatch = command.match(/^(\w+)=(.+)$/);
|
|
1667
|
+
if (valueMatch) {
|
|
1668
|
+
const key2 = valueMatch[1];
|
|
1669
|
+
const value = valueMatch[2];
|
|
1670
|
+
if (!(key2 in this.config)) return false;
|
|
1671
|
+
const currentValue = this.config[key2];
|
|
1672
|
+
if (typeof currentValue === "number") {
|
|
1673
|
+
const numValue = parseInt(value, 10);
|
|
1674
|
+
if (!isNaN(numValue)) {
|
|
1675
|
+
this.set(key2, numValue);
|
|
1676
|
+
return true;
|
|
1677
|
+
}
|
|
1678
|
+
} else if (typeof currentValue === "string") {
|
|
1679
|
+
this.set(key2, value);
|
|
1680
|
+
return true;
|
|
1681
|
+
}
|
|
1682
|
+
return false;
|
|
1683
|
+
}
|
|
1684
|
+
const key = command;
|
|
1685
|
+
if (key in this.config && typeof this.config[key] === "boolean") {
|
|
1686
|
+
this.set(key, true);
|
|
1687
|
+
return true;
|
|
1688
|
+
}
|
|
1689
|
+
return false;
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* Get a formatted string representation of a config value
|
|
1693
|
+
*/
|
|
1694
|
+
formatValue(key) {
|
|
1695
|
+
const value = this.config[key];
|
|
1696
|
+
if (typeof value === "boolean") {
|
|
1697
|
+
return value ? key : `no${key}`;
|
|
1698
|
+
}
|
|
1699
|
+
return `${key}=${value}`;
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* Get metadata about a config option
|
|
1703
|
+
*/
|
|
1704
|
+
getMetadata(key) {
|
|
1705
|
+
return CONFIG_METADATA[key];
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Get all options that differ from vanilla Vim
|
|
1709
|
+
*/
|
|
1710
|
+
getDifferencesFromVim() {
|
|
1711
|
+
return Object.entries(CONFIG_METADATA).filter(([_, meta]) => meta.differsFromVim).map(([key, _]) => key);
|
|
1712
|
+
}
|
|
1713
|
+
/**
|
|
1714
|
+
* Get options by category
|
|
1715
|
+
*/
|
|
1716
|
+
getByCategory(category) {
|
|
1717
|
+
return Object.entries(CONFIG_METADATA).filter(([_, meta]) => meta.category === category).map(([key, _]) => key);
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
var CONFIG_METADATA = {
|
|
1721
|
+
// Display options
|
|
1722
|
+
number: {
|
|
1723
|
+
description: "Show line numbers in the left margin",
|
|
1724
|
+
vimDefault: false,
|
|
1725
|
+
vimSimDefault: false,
|
|
1726
|
+
category: "display",
|
|
1727
|
+
type: "boolean",
|
|
1728
|
+
differsFromVim: false
|
|
1729
|
+
},
|
|
1730
|
+
relativenumber: {
|
|
1731
|
+
description: "Show line numbers relative to cursor position",
|
|
1732
|
+
vimDefault: false,
|
|
1733
|
+
vimSimDefault: false,
|
|
1734
|
+
category: "display",
|
|
1735
|
+
type: "boolean",
|
|
1736
|
+
differsFromVim: false
|
|
1737
|
+
},
|
|
1738
|
+
wrap: {
|
|
1739
|
+
description: "Wrap long lines to fit in window",
|
|
1740
|
+
vimDefault: true,
|
|
1741
|
+
vimSimDefault: true,
|
|
1742
|
+
category: "display",
|
|
1743
|
+
type: "boolean",
|
|
1744
|
+
differsFromVim: false
|
|
1745
|
+
},
|
|
1746
|
+
cursorline: {
|
|
1747
|
+
description: "Highlight the screen line of the cursor",
|
|
1748
|
+
vimDefault: false,
|
|
1749
|
+
vimSimDefault: false,
|
|
1750
|
+
category: "display",
|
|
1751
|
+
type: "boolean",
|
|
1752
|
+
differsFromVim: false
|
|
1753
|
+
},
|
|
1754
|
+
// Indentation options
|
|
1755
|
+
tabstop: {
|
|
1756
|
+
description: "Number of spaces that a <Tab> in the file counts for",
|
|
1757
|
+
vimDefault: 8,
|
|
1758
|
+
vimSimDefault: 4,
|
|
1759
|
+
category: "indentation",
|
|
1760
|
+
type: "number",
|
|
1761
|
+
differsFromVim: true
|
|
1762
|
+
},
|
|
1763
|
+
shiftwidth: {
|
|
1764
|
+
description: "Number of spaces to use for each step of (auto)indent",
|
|
1765
|
+
vimDefault: 8,
|
|
1766
|
+
vimSimDefault: 2,
|
|
1767
|
+
category: "indentation",
|
|
1768
|
+
type: "number",
|
|
1769
|
+
differsFromVim: true
|
|
1770
|
+
},
|
|
1771
|
+
expandtab: {
|
|
1772
|
+
description: "Use spaces instead of tabs when pressing <Tab>",
|
|
1773
|
+
vimDefault: false,
|
|
1774
|
+
vimSimDefault: true,
|
|
1775
|
+
category: "indentation",
|
|
1776
|
+
type: "boolean",
|
|
1777
|
+
differsFromVim: true
|
|
1778
|
+
},
|
|
1779
|
+
autoindent: {
|
|
1780
|
+
description: "Copy indent from current line when starting a new line",
|
|
1781
|
+
vimDefault: false,
|
|
1782
|
+
vimSimDefault: true,
|
|
1783
|
+
category: "indentation",
|
|
1784
|
+
type: "boolean",
|
|
1785
|
+
differsFromVim: true
|
|
1786
|
+
},
|
|
1787
|
+
smartindent: {
|
|
1788
|
+
description: "Do smart autoindenting when starting a new line",
|
|
1789
|
+
vimDefault: false,
|
|
1790
|
+
vimSimDefault: false,
|
|
1791
|
+
category: "indentation",
|
|
1792
|
+
type: "boolean",
|
|
1793
|
+
differsFromVim: false
|
|
1794
|
+
},
|
|
1795
|
+
// Search options
|
|
1796
|
+
ignorecase: {
|
|
1797
|
+
description: "Ignore case in search patterns",
|
|
1798
|
+
vimDefault: false,
|
|
1799
|
+
vimSimDefault: false,
|
|
1800
|
+
category: "search",
|
|
1801
|
+
type: "boolean",
|
|
1802
|
+
differsFromVim: false
|
|
1803
|
+
},
|
|
1804
|
+
smartcase: {
|
|
1805
|
+
description: "Override ignorecase if search pattern contains uppercase",
|
|
1806
|
+
vimDefault: false,
|
|
1807
|
+
vimSimDefault: false,
|
|
1808
|
+
category: "search",
|
|
1809
|
+
type: "boolean",
|
|
1810
|
+
differsFromVim: false
|
|
1811
|
+
},
|
|
1812
|
+
hlsearch: {
|
|
1813
|
+
description: "Highlight all matches for the search pattern",
|
|
1814
|
+
vimDefault: false,
|
|
1815
|
+
vimSimDefault: true,
|
|
1816
|
+
category: "search",
|
|
1817
|
+
type: "boolean",
|
|
1818
|
+
differsFromVim: true
|
|
1819
|
+
},
|
|
1820
|
+
incsearch: {
|
|
1821
|
+
description: "Show match for partly typed search pattern",
|
|
1822
|
+
vimDefault: false,
|
|
1823
|
+
vimSimDefault: true,
|
|
1824
|
+
category: "search",
|
|
1825
|
+
type: "boolean",
|
|
1826
|
+
differsFromVim: true
|
|
1827
|
+
},
|
|
1828
|
+
wrapscan: {
|
|
1829
|
+
description: "Searches wrap around the end of the file",
|
|
1830
|
+
vimDefault: true,
|
|
1831
|
+
vimSimDefault: true,
|
|
1832
|
+
category: "search",
|
|
1833
|
+
type: "boolean",
|
|
1834
|
+
differsFromVim: false
|
|
1835
|
+
},
|
|
1836
|
+
// Motion behavior options
|
|
1837
|
+
whichwrap: {
|
|
1838
|
+
description: "Keys that wrap to prev/next line (b=<BS>, s=<Space>, h=h, l=l, <=<Left>, >=<Right>, [=<Left> in insert, ]=<Right> in insert)",
|
|
1839
|
+
vimDefault: "b,s",
|
|
1840
|
+
vimSimDefault: "h,l",
|
|
1841
|
+
category: "motion",
|
|
1842
|
+
type: "string",
|
|
1843
|
+
differsFromVim: true,
|
|
1844
|
+
validValues: ["b", "s", "h", "l", "<", ">", "[", "]", "~"]
|
|
1845
|
+
},
|
|
1846
|
+
startofline: {
|
|
1847
|
+
description: "Move cursor to first non-blank when changing lines with G, gg, d, etc.",
|
|
1848
|
+
vimDefault: true,
|
|
1849
|
+
vimSimDefault: true,
|
|
1850
|
+
category: "motion",
|
|
1851
|
+
type: "boolean",
|
|
1852
|
+
differsFromVim: false
|
|
1853
|
+
},
|
|
1854
|
+
virtualedit: {
|
|
1855
|
+
description: "Allow cursor positioning where there is no character (block, insert, all, onemore)",
|
|
1856
|
+
vimDefault: "",
|
|
1857
|
+
vimSimDefault: "",
|
|
1858
|
+
category: "motion",
|
|
1859
|
+
type: "string",
|
|
1860
|
+
differsFromVim: false,
|
|
1861
|
+
validValues: ["block", "insert", "all", "onemore", ""]
|
|
1862
|
+
},
|
|
1863
|
+
// Editing options
|
|
1864
|
+
undolevels: {
|
|
1865
|
+
description: "Maximum number of changes that can be undone",
|
|
1866
|
+
vimDefault: 1e3,
|
|
1867
|
+
vimSimDefault: 1e3,
|
|
1868
|
+
category: "editing",
|
|
1869
|
+
type: "number",
|
|
1870
|
+
differsFromVim: false
|
|
1871
|
+
},
|
|
1872
|
+
clipboard: {
|
|
1873
|
+
description: "Use system clipboard for unnamed register",
|
|
1874
|
+
vimDefault: "",
|
|
1875
|
+
vimSimDefault: "",
|
|
1876
|
+
category: "editing",
|
|
1877
|
+
type: "string",
|
|
1878
|
+
differsFromVim: false,
|
|
1879
|
+
validValues: ["unnamed", "unnamedplus", ""]
|
|
1880
|
+
}
|
|
1881
|
+
};
|
|
1882
|
+
|
|
1550
1883
|
// src/core/state.ts
|
|
1551
1884
|
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) {
|
|
1885
|
+
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
1886
|
this.buffer = buffer;
|
|
1554
1887
|
this.cursor = cursor;
|
|
1555
1888
|
this.selection = selection;
|
|
@@ -1571,6 +1904,7 @@ var State = class {
|
|
|
1571
1904
|
this.completionManager = completionManager;
|
|
1572
1905
|
this.undoTree = undoTree;
|
|
1573
1906
|
this.lastCommand = lastCommand;
|
|
1907
|
+
this.configManager = configManager;
|
|
1574
1908
|
}
|
|
1575
1909
|
};
|
|
1576
1910
|
function createEmptyState() {
|
|
@@ -1596,8 +1930,10 @@ function createEmptyState() {
|
|
|
1596
1930
|
new CompletionManager(),
|
|
1597
1931
|
null,
|
|
1598
1932
|
// undoTree will be initialized separately
|
|
1599
|
-
null
|
|
1933
|
+
null,
|
|
1600
1934
|
// lastCommand
|
|
1935
|
+
new ConfigManager()
|
|
1936
|
+
// configManager with default settings
|
|
1601
1937
|
);
|
|
1602
1938
|
return state;
|
|
1603
1939
|
}
|
|
@@ -2139,7 +2475,17 @@ function withBufferContent(state, content) {
|
|
|
2139
2475
|
state.visualAnchor,
|
|
2140
2476
|
state.lastVisualSelection,
|
|
2141
2477
|
state.recordingRegister,
|
|
2142
|
-
state.lastMacroRegister
|
|
2478
|
+
state.lastMacroRegister,
|
|
2479
|
+
state.foldManager,
|
|
2480
|
+
state.markManager,
|
|
2481
|
+
state.jumpListManager,
|
|
2482
|
+
state.fileSystem,
|
|
2483
|
+
state.windowManager,
|
|
2484
|
+
state.lastSearch,
|
|
2485
|
+
state.spellChecker,
|
|
2486
|
+
state.completionManager,
|
|
2487
|
+
state.undoTree,
|
|
2488
|
+
state.lastCommand
|
|
2143
2489
|
);
|
|
2144
2490
|
}
|
|
2145
2491
|
function withCursor(state, cursor, preserveDesiredColumn = true) {
|
|
@@ -2154,7 +2500,17 @@ function withCursor(state, cursor, preserveDesiredColumn = true) {
|
|
|
2154
2500
|
state.visualAnchor,
|
|
2155
2501
|
state.lastVisualSelection,
|
|
2156
2502
|
state.recordingRegister,
|
|
2157
|
-
state.lastMacroRegister
|
|
2503
|
+
state.lastMacroRegister,
|
|
2504
|
+
state.foldManager,
|
|
2505
|
+
state.markManager,
|
|
2506
|
+
state.jumpListManager,
|
|
2507
|
+
state.fileSystem,
|
|
2508
|
+
state.windowManager,
|
|
2509
|
+
state.lastSearch,
|
|
2510
|
+
state.spellChecker,
|
|
2511
|
+
state.completionManager,
|
|
2512
|
+
state.undoTree,
|
|
2513
|
+
state.lastCommand
|
|
2158
2514
|
);
|
|
2159
2515
|
}
|
|
2160
2516
|
function withCursorVertical(state, cursor, targetColumn) {
|
|
@@ -2166,11 +2522,22 @@ function withCursorVertical(state, cursor, targetColumn) {
|
|
|
2166
2522
|
state.mode,
|
|
2167
2523
|
state.commandLine,
|
|
2168
2524
|
desiredCol,
|
|
2525
|
+
// Maintain desiredColumn for vertical movements
|
|
2169
2526
|
state.viewport,
|
|
2170
2527
|
state.visualAnchor,
|
|
2171
2528
|
state.lastVisualSelection,
|
|
2172
2529
|
state.recordingRegister,
|
|
2173
|
-
state.lastMacroRegister
|
|
2530
|
+
state.lastMacroRegister,
|
|
2531
|
+
state.foldManager,
|
|
2532
|
+
state.markManager,
|
|
2533
|
+
state.jumpListManager,
|
|
2534
|
+
state.fileSystem,
|
|
2535
|
+
state.windowManager,
|
|
2536
|
+
state.lastSearch,
|
|
2537
|
+
state.spellChecker,
|
|
2538
|
+
state.completionManager,
|
|
2539
|
+
state.undoTree,
|
|
2540
|
+
state.lastCommand
|
|
2174
2541
|
);
|
|
2175
2542
|
}
|
|
2176
2543
|
function withCursorHorizontal(state, cursor) {
|
|
@@ -2181,11 +2548,22 @@ function withCursorHorizontal(state, cursor) {
|
|
|
2181
2548
|
state.mode,
|
|
2182
2549
|
state.commandLine,
|
|
2183
2550
|
null,
|
|
2551
|
+
// Reset desiredColumn for horizontal movements
|
|
2184
2552
|
state.viewport,
|
|
2185
2553
|
state.visualAnchor,
|
|
2186
2554
|
state.lastVisualSelection,
|
|
2187
2555
|
state.recordingRegister,
|
|
2188
|
-
state.lastMacroRegister
|
|
2556
|
+
state.lastMacroRegister,
|
|
2557
|
+
state.foldManager,
|
|
2558
|
+
state.markManager,
|
|
2559
|
+
state.jumpListManager,
|
|
2560
|
+
state.fileSystem,
|
|
2561
|
+
state.windowManager,
|
|
2562
|
+
state.lastSearch,
|
|
2563
|
+
state.spellChecker,
|
|
2564
|
+
state.completionManager,
|
|
2565
|
+
state.undoTree,
|
|
2566
|
+
state.lastCommand
|
|
2189
2567
|
);
|
|
2190
2568
|
}
|
|
2191
2569
|
function withViewport(state, topLine) {
|
|
@@ -2471,18 +2849,24 @@ var B = class extends Motion {
|
|
|
2471
2849
|
};
|
|
2472
2850
|
|
|
2473
2851
|
// src/motions/basic.ts
|
|
2852
|
+
function canWrap(state, key) {
|
|
2853
|
+
const whichwrap = state.configManager?.get("whichwrap") ?? "h,l";
|
|
2854
|
+
const allowed = whichwrap.split(",").map((s) => s.trim());
|
|
2855
|
+
return allowed.includes(key);
|
|
2856
|
+
}
|
|
2474
2857
|
var h = class extends Motion {
|
|
2475
2858
|
key = "h";
|
|
2476
2859
|
execute(state, context) {
|
|
2477
2860
|
let count = getCount(context);
|
|
2478
2861
|
let line = state.cursor.line;
|
|
2479
2862
|
let col = state.cursor.column;
|
|
2863
|
+
const allowWrap = canWrap(state, "h");
|
|
2480
2864
|
while (count > 0) {
|
|
2481
2865
|
if (col > 0) {
|
|
2482
2866
|
const moveAmount = Math.min(col, count);
|
|
2483
2867
|
col -= moveAmount;
|
|
2484
2868
|
count -= moveAmount;
|
|
2485
|
-
} else if (line > 0) {
|
|
2869
|
+
} else if (line > 0 && allowWrap) {
|
|
2486
2870
|
line--;
|
|
2487
2871
|
const prevLine = getLine(state.buffer, line);
|
|
2488
2872
|
col = Math.max(0, prevLine.length - 1);
|
|
@@ -2494,11 +2878,53 @@ var h = class extends Motion {
|
|
|
2494
2878
|
return withCursorHorizontal(state, new Cursor(line, col));
|
|
2495
2879
|
}
|
|
2496
2880
|
};
|
|
2497
|
-
var Backspace = class extends
|
|
2881
|
+
var Backspace = class extends Motion {
|
|
2498
2882
|
key = "Backspace";
|
|
2883
|
+
execute(state, context) {
|
|
2884
|
+
let count = getCount(context);
|
|
2885
|
+
let line = state.cursor.line;
|
|
2886
|
+
let col = state.cursor.column;
|
|
2887
|
+
const allowWrap = canWrap(state, "b");
|
|
2888
|
+
while (count > 0) {
|
|
2889
|
+
if (col > 0) {
|
|
2890
|
+
const moveAmount = Math.min(col, count);
|
|
2891
|
+
col -= moveAmount;
|
|
2892
|
+
count -= moveAmount;
|
|
2893
|
+
} else if (line > 0 && allowWrap) {
|
|
2894
|
+
line--;
|
|
2895
|
+
const prevLine = getLine(state.buffer, line);
|
|
2896
|
+
col = Math.max(0, prevLine.length - 1);
|
|
2897
|
+
count--;
|
|
2898
|
+
} else {
|
|
2899
|
+
break;
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
return withCursorHorizontal(state, new Cursor(line, col));
|
|
2903
|
+
}
|
|
2499
2904
|
};
|
|
2500
|
-
var ArrowLeft = class extends
|
|
2905
|
+
var ArrowLeft = class extends Motion {
|
|
2501
2906
|
key = "ArrowLeft";
|
|
2907
|
+
execute(state, context) {
|
|
2908
|
+
let count = getCount(context);
|
|
2909
|
+
let line = state.cursor.line;
|
|
2910
|
+
let col = state.cursor.column;
|
|
2911
|
+
const allowWrap = canWrap(state, "<");
|
|
2912
|
+
while (count > 0) {
|
|
2913
|
+
if (col > 0) {
|
|
2914
|
+
const moveAmount = Math.min(col, count);
|
|
2915
|
+
col -= moveAmount;
|
|
2916
|
+
count -= moveAmount;
|
|
2917
|
+
} else if (line > 0 && allowWrap) {
|
|
2918
|
+
line--;
|
|
2919
|
+
const prevLine = getLine(state.buffer, line);
|
|
2920
|
+
col = Math.max(0, prevLine.length - 1);
|
|
2921
|
+
count--;
|
|
2922
|
+
} else {
|
|
2923
|
+
break;
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
return withCursorHorizontal(state, new Cursor(line, col));
|
|
2927
|
+
}
|
|
2502
2928
|
};
|
|
2503
2929
|
var l = class extends Motion {
|
|
2504
2930
|
key = "l";
|
|
@@ -2506,7 +2932,7 @@ var l = class extends Motion {
|
|
|
2506
2932
|
let count = getCount(context);
|
|
2507
2933
|
let line = state.cursor.line;
|
|
2508
2934
|
let col = state.cursor.column;
|
|
2509
|
-
const
|
|
2935
|
+
const allowWrap = canWrap(state, "l");
|
|
2510
2936
|
while (count > 0) {
|
|
2511
2937
|
const currentLine2 = getLine(state.buffer, line);
|
|
2512
2938
|
const maxCol = Math.max(0, currentLine2.length - 1);
|
|
@@ -2514,7 +2940,7 @@ var l = class extends Motion {
|
|
|
2514
2940
|
const moveAmount = Math.min(maxCol - col, count);
|
|
2515
2941
|
col += moveAmount;
|
|
2516
2942
|
count -= moveAmount;
|
|
2517
|
-
} else if (line < lastLine(state)) {
|
|
2943
|
+
} else if (line < lastLine(state) && allowWrap) {
|
|
2518
2944
|
line++;
|
|
2519
2945
|
col = 0;
|
|
2520
2946
|
count--;
|
|
@@ -2525,8 +2951,30 @@ var l = class extends Motion {
|
|
|
2525
2951
|
return withCursorHorizontal(state, new Cursor(line, col));
|
|
2526
2952
|
}
|
|
2527
2953
|
};
|
|
2528
|
-
var ArrowRight = class extends
|
|
2954
|
+
var ArrowRight = class extends Motion {
|
|
2529
2955
|
key = "ArrowRight";
|
|
2956
|
+
execute(state, context) {
|
|
2957
|
+
let count = getCount(context);
|
|
2958
|
+
let line = state.cursor.line;
|
|
2959
|
+
let col = state.cursor.column;
|
|
2960
|
+
const allowWrap = canWrap(state, ">");
|
|
2961
|
+
while (count > 0) {
|
|
2962
|
+
const currentLine2 = getLine(state.buffer, line);
|
|
2963
|
+
const maxCol = Math.max(0, currentLine2.length - 1);
|
|
2964
|
+
if (col < maxCol) {
|
|
2965
|
+
const moveAmount = Math.min(maxCol - col, count);
|
|
2966
|
+
col += moveAmount;
|
|
2967
|
+
count -= moveAmount;
|
|
2968
|
+
} else if (line < lastLine(state) && allowWrap) {
|
|
2969
|
+
line++;
|
|
2970
|
+
col = 0;
|
|
2971
|
+
count--;
|
|
2972
|
+
} else {
|
|
2973
|
+
break;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
return withCursorHorizontal(state, new Cursor(line, col));
|
|
2977
|
+
}
|
|
2530
2978
|
};
|
|
2531
2979
|
var j = class extends Motion {
|
|
2532
2980
|
key = "j";
|
|
@@ -2838,151 +3286,6 @@ var IndentationManager = class {
|
|
|
2838
3286
|
}
|
|
2839
3287
|
};
|
|
2840
3288
|
|
|
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
3289
|
// src/core/mode-transition.ts
|
|
2987
3290
|
var ModeTransition = class extends Command {
|
|
2988
3291
|
// Optional hook to modify state before mode transition
|
|
@@ -3841,7 +4144,7 @@ var gE = class extends Motion {
|
|
|
3841
4144
|
return repeatCursorMotion(state, context, findPrevWORDEnd);
|
|
3842
4145
|
}
|
|
3843
4146
|
};
|
|
3844
|
-
var
|
|
4147
|
+
var g_ = class extends Motion {
|
|
3845
4148
|
key = "g_";
|
|
3846
4149
|
inclusive = true;
|
|
3847
4150
|
// g_ is inclusive
|
|
@@ -3855,7 +4158,7 @@ var LastNonBlank = class extends Motion {
|
|
|
3855
4158
|
return withCursorHorizontal(state, new Cursor(state.cursor.line, col));
|
|
3856
4159
|
}
|
|
3857
4160
|
};
|
|
3858
|
-
var
|
|
4161
|
+
var gm = class extends Motion {
|
|
3859
4162
|
key = "gm";
|
|
3860
4163
|
execute(state) {
|
|
3861
4164
|
const line = getLine(state.buffer, state.cursor.line);
|
|
@@ -6234,18 +6537,11 @@ var Session = class _Session {
|
|
|
6234
6537
|
}
|
|
6235
6538
|
this.keyBuffer.push(key);
|
|
6236
6539
|
const input = this.keyBuffer.join("");
|
|
6237
|
-
console.log("[DEBUG] handleKey - input:", input, "keyBuffer:", this.keyBuffer);
|
|
6238
6540
|
const result = this.match(input, this.keyBuffer);
|
|
6239
|
-
console.log("[DEBUG] handleKey - match result:", result ? result === "partial" ? "partial" : `complete: ${result.command.key}` : "null");
|
|
6240
6541
|
if (result && result !== "partial" && result.isComplete) {
|
|
6241
6542
|
const stateBefore = this.state;
|
|
6242
6543
|
result.context.registerManager = this.registerManager;
|
|
6243
6544
|
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
6545
|
this.state = result.command.execute(this.state, result.context);
|
|
6250
6546
|
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
6547
|
stateBefore.visualAnchor && (this.state.cursor.line !== stateBefore.cursor.line || this.state.cursor.column !== stateBefore.cursor.column)) {
|
|
@@ -6648,8 +6944,8 @@ var Session = class _Session {
|
|
|
6648
6944
|
addMotionAllModes(new MatchPercent());
|
|
6649
6945
|
addMotionAllModes(new ge());
|
|
6650
6946
|
addMotionAllModes(new gE());
|
|
6651
|
-
addMotionAllModes(new
|
|
6652
|
-
addMotionAllModes(new
|
|
6947
|
+
addMotionAllModes(new g_());
|
|
6948
|
+
addMotionAllModes(new gm());
|
|
6653
6949
|
addMotionAllModes(new MiddleOfLine());
|
|
6654
6950
|
addMotionAllModes(new Column());
|
|
6655
6951
|
addMotionAllModes(new Star());
|
|
@@ -6845,6 +7141,7 @@ var PatternRegistry = class {
|
|
|
6845
7141
|
};
|
|
6846
7142
|
export {
|
|
6847
7143
|
Buffer,
|
|
7144
|
+
CONFIG_METADATA,
|
|
6848
7145
|
Change,
|
|
6849
7146
|
Command,
|
|
6850
7147
|
ConfigManager,
|