@@ -230,7 +230,7 @@ public class TextEditor extends JFrame implements ActionListener,
230230 private FindAndReplaceDialog findDialog ;
231231 private JCheckBoxMenuItem autoSave , wrapLines , tabsEmulated , autoImport ,
232232 autocompletion , fallbackAutocompletion , keylessAutocompletion ,
233- markOccurences , paintTabs , whiteSpace , marginLine , lockPane ;
233+ markOccurences , paintTabs , whiteSpace , marginLine , lockPane , vimMode ;
234234 private ButtonGroup themeRadioGroup ;
235235 private JTextArea errorScreen = new ConsoleArea (ConsoleArea .Content .ERRORS );
236236
@@ -648,6 +648,9 @@ public TextEditor(final Context context) {
648648 options .add (keylessAutocompletion );
649649 options .add (fallbackAutocompletion );
650650
651+ GuiUtils .addMenubarSeparator (options , "Key Bindings:" );
652+ options .add (vimMode );
653+
651654 options .addSeparator ();
652655 appendPreferences (options );
653656 mbar .add (options );
@@ -1130,6 +1133,10 @@ private void initializeDynamicMenuComponents() {
11301133 fallbackAutocompletion .setToolTipText ("<HTML>If selected, Java completions will be used when scripting<br>"
11311134 + "a language for which auto-completions are not available" );
11321135 fallbackAutocompletion .addItemListener (e -> setFallbackAutoCompletion (fallbackAutocompletion .getState ()));
1136+ vimMode = new JCheckBoxMenuItem ("Vim Mode" , false );
1137+ vimMode .setToolTipText ("<HTML>Edit with vim-style modal key bindings.<br>"
1138+ + "Press Esc for normal mode; :w saves, :q closes the tab" );
1139+ vimMode .addItemListener (e -> setVimMode (vimMode .getState ()));
11331140 themeRadioGroup = new ButtonGroup ();
11341141
11351142 // Help menu. These are 'dynamic' items
@@ -1647,15 +1654,7 @@ else if (source == previousError) {
16471654 getEditorPane ().getErrorHighlighter ().gotoPreviousError ();
16481655 }
16491656 else if (source == kill ) chooseTaskToKill ();
1650- else if (source == close ) if (tabbed .getTabCount () < 2 ) processWindowEvent (new WindowEvent (
1651- this , WindowEvent .WINDOW_CLOSING ));
1652- else {
1653- if (!handleUnsavedChanges ()) return ;
1654- int index = tabbed .getSelectedIndex ();
1655- removeTab (index );
1656- if (index > 0 ) index --;
1657- switchTo (index );
1658- }
1657+ else if (source == close ) closeTab (true );
16591658 else if (source == copy ) getTextArea ().copy ();
16601659 else if (source == find ) findOrReplace (true );
16611660 else if (source == findNext ) {
@@ -1812,6 +1811,55 @@ private void setMarginLineEnabled(final boolean enabled) {
18121811 getEditorPane ().requestFocusInWindow ();
18131812 }
18141813
1814+ /**
1815+ * Closes the current tab, or the window if it is the last tab.
1816+ *
1817+ * @param confirm Whether to ask about unsaved changes before closing a tab.
1818+ */
1819+ private void closeTab (final boolean confirm ) {
1820+ if (tabbed .getTabCount () < 2 ) {
1821+ processWindowEvent (new WindowEvent (this , WindowEvent .WINDOW_CLOSING ));
1822+ return ;
1823+ }
1824+ if (confirm && !handleUnsavedChanges ()) return ;
1825+ int index = tabbed .getSelectedIndex ();
1826+ removeTab (index );
1827+ if (index > 0 ) index --;
1828+ switchTo (index );
1829+ }
1830+
1831+ /**
1832+ * Handles a vim ex command (typed after ':') which the editor pane does not
1833+ * handle itself.
1834+ *
1835+ * @return false if the command is unknown.
1836+ */
1837+ boolean handleVimCommand (final String command ) {
1838+ switch (command ) {
1839+ case "w" :
1840+ save ();
1841+ return true ;
1842+ case "q" :
1843+ closeTab (true );
1844+ return true ;
1845+ case "q!" :
1846+ closeTab (false );
1847+ return true ;
1848+ case "wq" :
1849+ case "x" :
1850+ if (save ()) closeTab (true );
1851+ return true ;
1852+ default :
1853+ return false ;
1854+ }
1855+ }
1856+
1857+ private void setVimMode (final boolean enabled ) {
1858+ for (int i = 0 ; i < tabbed .getTabCount (); i ++)
1859+ getEditorPane (i ).setVimEnabled (enabled );
1860+ getEditorPane ().requestFocusInWindow ();
1861+ }
1862+
18151863 private JMenu applyThemeMenu () {
18161864 final LinkedHashMap <String , String > map = new LinkedHashMap <>();
18171865 map .put ("Default" , "default" );
@@ -2487,6 +2535,7 @@ else if (tabSize == Integer.parseInt(item.getText())) {
24872535 }
24882536 }
24892537 markOccurences .setState (pane .getMarkOccurrences ());
2538+ vimMode .setState (pane .isVimEnabled ());
24902539 wrapLines .setState (pane .getLineWrap ());
24912540 marginLine .setState (pane .isMarginLineEnabled ());
24922541 tabsEmulated .setState (pane .getTabsEmulated ());
0 commit comments