diff --git a/.ecode/project_build.json b/.ecode/project_build.json index 1f9c368d4..91f3be7bd 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -406,7 +406,7 @@ "eepp-linux-ninja": { "build": [ { - "args": "--disable-static-build --with-text-shaper ninja", + "args": "--disable-static-build --with-text-shaper --with-debug-symbols ninja", "command": "premake5", "working_dir": "${project_root}" }, diff --git a/bin/assets/plugins/aiassistant.json b/bin/assets/plugins/aiassistant.json index a4ee8261b..eb91ff353 100644 --- a/bin/assets/plugins/aiassistant.json +++ b/bin/assets/plugins/aiassistant.json @@ -375,6 +375,48 @@ "tool_calling": true } ] + }, + "openrouter": { + "api_url": "https://openrouter.ai/api/v1/chat/completions", + "display_name": "OpenRouter", + "models": [ + { + "name": "x-ai/grok-4-fast:free", + "display_name": "xAI: Grok 4 Fast (free)" + }, + { + "name": "deepseek/deepseek-chat-v3.1:free", + "display_name": "DeepSeek: DeepSeek V3.1 (free)" + }, + { + "name": "x-ai/grok-code-fast-1", + "display_name": "xAI: Grok Code Fast 1" + }, + { + "name": "anthropic/claude-sonnet-4", + "display_name": "Anthropic: Claude Sonnet 4" + }, + { + "name": "google/gemini-2.5-flash", + "display_name": "Google: Gemini 2.5 Flash" + }, + { + "name": "deepseek/deepseek-chat-v3-0324", + "display_name": "DeepSeek: DeepSeek V3 0324" + }, + { + "name": "google/gemini-2.5-pro", + "display_name": "Google: Gemini 2.5 Pro" + }, + { + "name": "openai/gpt-5", + "display_name": "OpenAI: GPT-5" + }, + { + "name": "google/gemini-2.5-flash-lite", + "display_name": "Google: Gemini 2.5 Flash Lite" + } + ] } } } diff --git a/include/eepp/ui/doc/textdocument.hpp b/include/eepp/ui/doc/textdocument.hpp index 219fb5741..8af0bf724 100644 --- a/include/eepp/ui/doc/textdocument.hpp +++ b/include/eepp/ui/doc/textdocument.hpp @@ -239,7 +239,7 @@ class EE_API TextDocument { TextPosition positionOffset( TextPosition position, TextPosition offset ) const; - bool replaceLine( const Int64& lineNum, const String& text ); + bool replaceLine( Int64 lineNum, const String& text ); bool replaceCurrentLine( const String& text ); @@ -357,6 +357,8 @@ class EE_API TextDocument { void deleteToNextChar(); + void deleteToStartOfLine(); + void deleteToEndOfLine(); void deleteToPreviousWord(); @@ -705,6 +707,8 @@ class EE_API TextDocument { void fromBase64(); + void trimTrailingWhitespace(); + protected: friend class TextUndoStack; friend class FoldRangeService; diff --git a/src/eepp/ui/doc/textdocument.cpp b/src/eepp/ui/doc/textdocument.cpp index bb38bf61d..a90667776 100644 --- a/src/eepp/ui/doc/textdocument.cpp +++ b/src/eepp/ui/doc/textdocument.cpp @@ -1567,7 +1567,7 @@ TextPosition TextDocument::positionOffset( TextPosition position, TextPosition o return sanitizePosition( position + offset ); } -bool TextDocument::replaceLine( const Int64& lineNum, const String& text ) { +bool TextDocument::replaceLine( Int64 lineNum, const String& text ) { if ( lineNum >= 0 && lineNum < (Int64)linesCount() ) { TextRange oldSelection = getSelection(); setSelection( { startOfLine( { lineNum, 0 } ), endOfLine( { lineNum, 0 } ) } ); @@ -2200,6 +2200,12 @@ void TextDocument::deleteToNextChar() { mergeSelection(); } +void TextDocument::deleteToStartOfLine() { + for ( size_t i = 0; i < mSelection.size(); ++i ) + deleteTo( i, startOfLine( getSelectionIndex( i ).start() ) ); + mergeSelection(); +} + void TextDocument::deleteToEndOfLine() { for ( size_t i = 0; i < mSelection.size(); ++i ) deleteTo( i, endOfLine( getSelectionIndex( i ).start() ) ); @@ -4111,6 +4117,21 @@ void TextDocument::fromBase64() { } } +void TextDocument::trimTrailingWhitespace() { + BoolScopedOpOptional op( !mDoingTextInput, mDoingTextInput, true ); + for ( size_t i = 0; i < linesCount(); i++ ) { + safeLineOp( i, [&]( TextDocumentLine& op ) { + if ( op.size() > 1 && ( op[op.size() - 2] == ' ' || op[op.size() - 2] == '\t' ) ) { + String text( op.getText() ); + text.pop_back(); // Remove '\n' + while ( !text.empty() && ( text.back() == ' ' || text.back() == '\t' ) ) + text.pop_back(); + replaceLine( i, text ); + } + } ); + } +} + void TextDocument::initializeCommands() { mCommands["reset"] = [this] { reset(); }; mCommands["save"] = [this] { save(); }; @@ -4119,6 +4140,7 @@ void TextDocument::initializeCommands() { mCommands["delete-to-next-word"] = [this] { deleteToNextWord(); }; mCommands["delete-to-next-char"] = [this] { deleteToNextChar(); }; mCommands["delete-current-line"] = [this] { deleteCurrentLine(); }; + mCommands["delete-to-start-of-line"] = [this] { deleteToStartOfLine(); }; mCommands["delete-to-end-of-line"] = [this] { deleteToEndOfLine(); }; mCommands["delete-selection"] = [this] { deleteSelection(); }; mCommands["delete-word"] = [this] { deleteWord(); }; @@ -4174,6 +4196,7 @@ void TextDocument::initializeCommands() { mCommands["unescape"] = [this] { unescape(); }; mCommands["to-base64"] = [this] { toBase64(); }; mCommands["from-base64"] = [this] { fromBase64(); }; + mCommands["trim-trailing-whitespace"] = [this] { trimTrailingWhitespace(); }; if ( TEXT_DOCUMENT_COMMANDS.empty() ) { for ( const auto& [cmd, _] : mCommands ) diff --git a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp index dff2722ae..59d4466e9 100644 --- a/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp +++ b/src/tools/ecode/plugins/aiassistant/aiassistantplugin.cpp @@ -328,6 +328,11 @@ void AIAssistantPlugin::loadAIAssistantConfig( const std::string& path, bool upd else if ( updateConfigFile ) config["perplexity_api_key"] = mApiKeys["perplexity"]; + if ( config.contains( "openrouter_api_key" ) ) + mApiKeys["openrouter"] = config.value( "openrouter_api_key", "" ); + else if ( updateConfigFile ) + config["openrouter_api_key"] = mApiKeys["openrouter"]; + } if ( mKeyBindings.empty() ) { @@ -511,7 +516,10 @@ std::optional AIAssistantPlugin::getApiKeyFromProvider( const std:: ret = getenv( "GITHUB_API_KEY" ); } else if ( provider == "perplexity" ) { ret = getenv( "PERPLEXITY_API_KEY" ); + } else if ( provider == "openrouter" ) { + ret = getenv( "OPENROUTER_API_KEY" ); } + if ( ret ) return std::string{ ret };