Added trim-trailing-whitespace command (SpartanJ/ecode#667).

Added `delete-to-start-of-line` command (SpartanJ/ecode#668).
Added OpenRouter support to the AI Assistant.
This commit is contained in:
Martín Lucas Golini
2025-09-30 01:13:24 -03:00
parent 5f666007bf
commit 3248543ce2
5 changed files with 80 additions and 3 deletions

View File

@@ -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}"
},

View File

@@ -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"
}
]
}
}
}

View File

@@ -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;

View File

@@ -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 )

View File

@@ -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<std::string> 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 };