diff --git a/bin/assets/i18n/de.xml b/bin/assets/i18n/de.xml
index 767bcf720..d11d2d18e 100644
--- a/bin/assets/i18n/de.xml
+++ b/bin/assets/i18n/de.xml
@@ -965,4 +965,8 @@ Verwendet strftime-Formatbezeichner. Die Dateierweiterung richtet sich nach dem
Aus
Standard
Ein
+ Tab-Ausgang
+ Wenn Tab vor einem konfigurierten Zeichen gedrückt wird, springt der Cursor darüber. Gilt nur für einen einzelnen Cursor ohne Auswahl.
+ Tab-Ausgangszeichen festlegen
+ Legen Sie die Zeichen fest, über die der Cursor beim Drücken von Tab springen kann.
diff --git a/bin/assets/i18n/en.xml b/bin/assets/i18n/en.xml
index 21e5eee05..d9582ab61 100644
--- a/bin/assets/i18n/en.xml
+++ b/bin/assets/i18n/en.xml
@@ -949,4 +949,8 @@ Uses strftime format specifiers. The file extension follows the selected screens
Off
Default
On
+ Tab Out
+ Pressing Tab before a configured character moves the cursor past it. Only applies to a single cursor without a selection.
+ Set Tab Out Characters
+ Set the characters the cursor can move past when pressing Tab.
diff --git a/bin/assets/i18n/fr.xml b/bin/assets/i18n/fr.xml
index b508b0420..a6815adf3 100644
--- a/bin/assets/i18n/fr.xml
+++ b/bin/assets/i18n/fr.xml
@@ -944,4 +944,8 @@ Utilise les spécificateurs de format strftime. L’extension du fichier corresp
Désactivé
Par défaut
Activé
+ Sortie par tabulation
+ Appuyer sur Tab avant un caractère configuré déplace le curseur après celui-ci. S'applique uniquement à un curseur sans sélection.
+ Définir les caractères de sortie
+ Définissez les caractères que le curseur peut franchir en appuyant sur Tab.
diff --git a/bin/assets/i18n/zh.xml b/bin/assets/i18n/zh.xml
index 481214551..9c84db85d 100644
--- a/bin/assets/i18n/zh.xml
+++ b/bin/assets/i18n/zh.xml
@@ -723,4 +723,8 @@ file in the directory tree.
关闭
默认
开启
+ Tab 跳出
+ 在已配置的字符前按 Tab 可将光标移至该字符之后。仅适用于无选区的单个光标。
+ 设置 Tab 跳出字符
+ 设置按 Tab 时光标可以跳过的字符。
diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp
index 33e778ad5..99cedb150 100644
--- a/include/eepp/ui/doc/textdocument.hpp
+++ b/include/eepp/ui/doc/textdocument.hpp
@@ -564,6 +564,14 @@ class EE_API TextDocument {
const std::vector>&
autoCloseBracketsPairs );
+ bool getTabOutEnabled() const;
+
+ void setTabOutEnabled( bool enabled );
+
+ const String& getTabOutChars() const;
+
+ void setTabOutChars( const String& chars );
+
bool isDirtyOnFileSystem() const;
void setDirtyOnFileSystem( bool dirtyOnFileSystem );
@@ -780,6 +788,7 @@ class EE_API TextDocument {
bool mTrimTrailingWhitespaces{ false };
bool mVerbose{ false };
bool mAutoCloseBrackets{ false };
+ bool mTabOutEnabled{ false };
bool mDirtyOnFileSystem{ false };
bool mSaving{ false };
bool mDeleteOnClose{ false };
@@ -789,6 +798,7 @@ class EE_API TextDocument {
bool mDoingTextInput{ false };
bool mInsertingText{ false };
std::vector> mAutoCloseBracketsPairs;
+ String mTabOutChars{ ")]}'\":;>," };
Uint32 mIndentWidth{ 4 };
IndentType mIndentType{ IndentType::IndentTabs };
AutoIndentConfig mAutoIndent{ AutoIndentConfig::Smart };
diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp
index b36f60efd..48684abe0 100644
--- a/src/eepp/ui/doc/textdocument.cpp
+++ b/src/eepp/ui/doc/textdocument.cpp
@@ -2906,6 +2906,12 @@ void TextDocument::removeFromStartOfSelectedLines( const String& text, bool skip
}
void TextDocument::indent() {
+ if ( mTabOutEnabled && mSelection.size() == 1 && !hasSelection() &&
+ mTabOutChars.find( getCurrentChar() ) != String::InvalidPos ) {
+ moveToNextChar();
+ return;
+ }
+
if ( hasSelection() ) {
insertAtStartOfSelectedLines( getIndentString(), false );
} else {
@@ -3042,6 +3048,22 @@ void TextDocument::setAutoCloseBracketsPairs(
mAutoCloseBracketsPairs = autoCloseBracketsPairs;
}
+bool TextDocument::getTabOutEnabled() const {
+ return mTabOutEnabled;
+}
+
+void TextDocument::setTabOutEnabled( bool enabled ) {
+ mTabOutEnabled = enabled;
+}
+
+const String& TextDocument::getTabOutChars() const {
+ return mTabOutChars;
+}
+
+void TextDocument::setTabOutChars( const String& chars ) {
+ mTabOutChars = chars;
+}
+
bool TextDocument::isDirtyOnFileSystem() const {
return mDirtyOnFileSystem;
}
diff --git a/src/tests/unit_tests/textdocument_tests.cpp b/src/tests/unit_tests/textdocument_tests.cpp
index a432a2e63..06b36a855 100644
--- a/src/tests/unit_tests/textdocument_tests.cpp
+++ b/src/tests/unit_tests/textdocument_tests.cpp
@@ -294,6 +294,73 @@ UTEST( TextDocument, newLineNormal ) {
EXPECT_STDSTREQ( TextPosition( 1, 2 ).toString(), doc.getSelection().start().toString() );
}
+UTEST( TextDocument, indentTabsOutOfTrailingCharactersWithSingleCursor ) {
+ static constexpr char trailingCharacters[] = ")]}'\":;>,";
+
+ for ( const char trailingCharacter : trailingCharacters ) {
+ if ( trailingCharacter == '\0' )
+ break;
+
+ TextDocument doc;
+ doc.setIndentType( TextDocument::IndentType::IndentTabs );
+ doc.setTabOutEnabled( true );
+ doc.insert( 0, { 0, 0 }, String( trailingCharacter ) );
+ doc.setSelection( { 0, 0 } );
+ doc.indent();
+
+ EXPECT_STRINGEQ( String( trailingCharacter ) + "\n", doc.line( 0 ).getText() );
+ EXPECT_STDSTREQ( TextPosition( 0, 1 ).toString(), doc.getSelection().start().toString() );
+ }
+}
+
+UTEST( TextDocument, indentFallsBackToExistingBehavior ) {
+ TextDocument doc;
+ doc.setIndentType( TextDocument::IndentType::IndentTabs );
+ doc.setTabOutEnabled( true );
+ doc.insert( 0, { 0, 0 }, ") ordinary" );
+
+ // A non-trailing character still inserts indentation.
+ doc.setSelection( { 0, 2 } );
+ doc.indent();
+ EXPECT_STRINGEQ( ") \tordinary\n", doc.line( 0 ).getText() );
+
+ // A selection still indents the selected line, even before a trailing character.
+ doc.setSelection( { { 0, 0 }, { 0, 1 } } );
+ doc.indent();
+ EXPECT_STRINGEQ( "\t) \tordinary\n", doc.line( 0 ).getText() );
+}
+
+UTEST( TextDocument, indentDoesNotTabOutWithMultipleCursors ) {
+ TextDocument doc;
+ doc.setIndentType( TextDocument::IndentType::IndentTabs );
+ doc.setTabOutEnabled( true );
+ doc.insert( 0, { 0, 0 }, ")\n}" );
+ doc.resetSelection( TextRanges( std::vector{ TextRange( { 0, 0 }, { 0, 0 } ),
+ TextRange( { 1, 0 }, { 1, 0 } ) } ) );
+ doc.indent();
+
+ EXPECT_STRINGEQ( "\t)\n", doc.line( 0 ).getText() );
+ EXPECT_STRINGEQ( "\t}\n", doc.line( 1 ).getText() );
+}
+
+UTEST( TextDocument, indentTabOutIsOptionalAndConfigurable ) {
+ TextDocument doc;
+ doc.setIndentType( TextDocument::IndentType::IndentTabs );
+ doc.insert( 0, { 0, 0 }, ")x" );
+ doc.setSelection( { 0, 0 } );
+
+ // Disabled by default.
+ doc.indent();
+ EXPECT_STRINGEQ( "\t)x\n", doc.line( 0 ).getText() );
+
+ // A custom set replaces the defaults.
+ doc.setTabOutEnabled( true );
+ doc.setTabOutChars( "x" );
+ doc.setSelection( { 0, 2 } );
+ doc.indent();
+ EXPECT_STDSTREQ( TextPosition( 0, 3 ).toString(), doc.getSelection().start().toString() );
+}
+
UTEST( TextDocument, moveToStartOfContent ) {
TextDocument doc;
doc.insert( 0, { 0, 0 }, " content" );
diff --git a/src/tools/ecode/appconfig.cpp b/src/tools/ecode/appconfig.cpp
index 3b8a841a6..3740ef2dc 100644
--- a/src/tools/ecode/appconfig.cpp
+++ b/src/tools/ecode/appconfig.cpp
@@ -184,6 +184,8 @@ void AppConfig::load( const std::string& confPath, std::string& keybindingsPath,
doc.indentWidth = ini.getValueI( "document", "indent_width", 4 );
doc.indentSpaces = ini.getValueB( "document", "indent_spaces", false );
doc.tabStops = ini.getValueB( "document", "tab_stops", true );
+ doc.tabOutEnabled = ini.getValueB( "document", "tab_out_enabled", false );
+ doc.tabOutChars = ini.getValue( "document", "tab_out_chars", ")]}'\":;>," );
doc.lineEndings =
TextFormat::stringToLineEnding( ini.getValue( "document", "line_endings", "LF" ) );
editor.newTabPosition =
@@ -395,6 +397,8 @@ void AppConfig::save( const std::vector& recentFiles,
ini.setValueB( "document", "write_bom", doc.writeUnicodeBOM );
ini.setValueI( "document", "indent_width", doc.indentWidth );
ini.setValueB( "document", "tab_stops", doc.tabStops );
+ ini.setValueB( "document", "tab_out_enabled", doc.tabOutEnabled );
+ ini.setValue( "document", "tab_out_chars", doc.tabOutChars );
ini.setValueB( "document", "indent_spaces", doc.indentSpaces );
ini.setValue( "document", "line_endings", TextFormat::lineEndingToString( doc.lineEndings ) );
ini.setValueI( "document", "tab_width", doc.tabWidth );
diff --git a/src/tools/ecode/appconfig.hpp b/src/tools/ecode/appconfig.hpp
index 408d3ab69..a222b061a 100644
--- a/src/tools/ecode/appconfig.hpp
+++ b/src/tools/ecode/appconfig.hpp
@@ -156,8 +156,10 @@ struct DocumentConfig {
bool writeUnicodeBOM{ false };
bool indentSpaces{ false };
bool tabStops{ true };
+ bool tabOutEnabled{ false };
TextFormat::LineEnding lineEndings{ TextFormat::LineEnding::LF };
TextDocument::AutoIndentConfig autoIndent{ TextDocument::AutoIndentConfig::Smart };
+ std::string tabOutChars{ ")]}'\":;>," };
int indentWidth{ 4 };
int tabWidth{ 4 };
int lineBreakingColumn{ 100 };
diff --git a/src/tools/ecode/ecode.cpp b/src/tools/ecode/ecode.cpp
index 5fba64f0d..7f1d8b2b6 100644
--- a/src/tools/ecode/ecode.cpp
+++ b/src/tools/ecode/ecode.cpp
@@ -2985,6 +2985,8 @@ void App::onCodeEditorCreated( UICodeEditor* editor, TextDocument& doc ) {
doc.setAutoCloseBrackets( !mConfig.editor.autoCloseBrackets.empty() );
doc.setAutoCloseBracketsPairs( makeAutoClosePairs( mConfig.editor.autoCloseBrackets ) );
+ doc.setTabOutEnabled( mConfig.doc.tabOutEnabled );
+ doc.setTabOutChars( String::fromUtf8( mConfig.doc.tabOutChars ) );
doc.setLineEnding( docc.lineEndings );
doc.setTrimTrailingWhitespaces( docc.trimTrailingWhitespaces );
doc.setForceNewLineAtEndOfFile( docc.forceNewLineAtEndOfFile );
diff --git a/src/tools/ecode/settingsactions.cpp b/src/tools/ecode/settingsactions.cpp
index 7965036e3..70eb718b5 100644
--- a/src/tools/ecode/settingsactions.cpp
+++ b/src/tools/ecode/settingsactions.cpp
@@ -251,6 +251,25 @@ void SettingsActions::setIndentTabCharacter() {
mApp->setFocusEditorOnClose( msgBox );
}
+void SettingsActions::setTabOutChars() {
+ UIMessageBox* msgBox = UIMessageBox::New(
+ UIMessageBox::INPUT,
+ i18n( "set_tab_out_characters_message",
+ "Set the characters the cursor can move past when pressing Tab." ) );
+ msgBox->setTitle( i18n( "set_tab_out_characters", "Set Tab Out Characters" ) );
+ msgBox->setCloseShortcut( { KEY_ESCAPE, 0 } );
+ msgBox->getTextInput()->setText( String::fromUtf8( mApp->getConfig().doc.tabOutChars ) );
+ msgBox->showWhenReady();
+ msgBox->on( Event::OnConfirm, [this, msgBox]( const Event* ) {
+ String chars = msgBox->getTextInput()->getText();
+ mApp->getConfig().doc.tabOutChars = chars.toUtf8();
+ mApp->getSplitter()->forEachEditor(
+ [&chars]( UICodeEditor* editor ) { editor->getDocument().setTabOutChars( chars ); } );
+ msgBox->closeWindow();
+ } );
+ mApp->setFocusEditorOnClose( msgBox );
+}
+
void SettingsActions::setFoldRefreshFreq() {
UIMessageBox* msgBox = UIMessageBox::New(
UIMessageBox::INPUT,
diff --git a/src/tools/ecode/settingsactions.hpp b/src/tools/ecode/settingsactions.hpp
index 38c04c98c..70068b271 100644
--- a/src/tools/ecode/settingsactions.hpp
+++ b/src/tools/ecode/settingsactions.hpp
@@ -25,6 +25,8 @@ class SettingsActions {
void setIndentTabCharacter();
+ void setTabOutChars();
+
void setFoldRefreshFreq();
void setUIScaleFactor();
diff --git a/src/tools/ecode/settingsmenu.cpp b/src/tools/ecode/settingsmenu.cpp
index 08045ed41..8102b6a5a 100644
--- a/src/tools/ecode/settingsmenu.cpp
+++ b/src/tools/ecode/settingsmenu.cpp
@@ -824,6 +824,14 @@ UIMenu* SettingsMenu::createDocumentMenu() {
"like a fixed number of spaces regardless of their position." ) )
->setId( "tab_stops" );
+ mGlobalMenu->addCheckBox( i18n( "tab_out", "Tab Out" ), mApp->getConfig().doc.tabOutEnabled )
+ ->setTooltipText( i18n(
+ "tab_out_desc", "Pressing Tab before a configured character moves the cursor past it. "
+ "Only applies to a single cursor without a selection." ) )
+ ->setId( "tab_out" );
+ mGlobalMenu->add( i18n( "set_tab_out_characters", "Set Tab Out Characters" ) )
+ ->setId( "set_tab_out_characters" );
+
mGlobalMenu
->addCheckBox( i18n( "force_new_line_at_end_of_file", "Force New Line at End of File" ),
mApp->getConfig().doc.forceNewLineAtEndOfFile )
@@ -948,6 +956,11 @@ UIMenu* SettingsMenu::createDocumentMenu() {
mSplitter->forEachEditor( [this]( UICodeEditor* editor ) {
editor->setTabStops( mApp->getConfig().doc.tabStops );
} );
+ } else if ( "tab_out" == id ) {
+ mApp->getConfig().doc.tabOutEnabled = item->isActive();
+ mSplitter->forEachEditor( [this]( UICodeEditor* editor ) {
+ editor->getDocument().setTabOutEnabled( mApp->getConfig().doc.tabOutEnabled );
+ } );
}
} else if ( "line_breaking_column" == id ) {
mApp->getSettingsActions()->setLineBreakingColumn();
@@ -957,6 +970,8 @@ UIMenu* SettingsMenu::createDocumentMenu() {
mApp->getSettingsActions()->setCursorBlinkingTime();
} else if ( "indent_tab_character" == id ) {
mApp->getSettingsActions()->setIndentTabCharacter();
+ } else if ( "set_tab_out_characters" == id ) {
+ mApp->getSettingsActions()->setTabOutChars();
}
} );