Repository: Shopify/ruby-lsp (Ruby)

Issue:
Support Windsurf for placing cursor in correct place after auto complete end and |
Creating a block creates `end` for me, but positions the cursor after. Typing a `|` for a block arg types a closing `|` and positions the cursor after.


Typing `class Hotdog<enter>` yields
```rb
class Hotdog
end_
   ^-- cursor
```

Typing `things.each do |` yields
```rb
things.each do ||_
                 ^--- cursor after 2nd |
```

This creates quite a bit of extra chaos in an environment where AI is already trying to wreck your code for you ;)

This was presented in a [recent issue](https://github.com/Shopify/ruby-lsp/issues/3156) related to VSCodium, which was resolved by specifying [that fork in a list of clients able to position the cursor](https://github.com/Shopify/ruby-lsp/pull/3302).

There was also [some conversation](https://github.com/Shopify/ruby-lsp/pull/3302/files#r2000964016) about the whack-a-mole nature of supporting VSCode forks where Vini proposed using predicate methods instead of a string of all the forks under the sun.

I'd appreciate if Windsurf could be made compatible as well, either by way of whitelisting, or a more robust solution.


Discussion:

Patch:
diff --git a/lib/ruby_lsp/requests/on_type_formatting.rb b/lib/ruby_lsp/requests/on_type_formatting.rb
index 722527a094..257341deec 100644
--- a/lib/ruby_lsp/requests/on_type_formatting.rb
+++ b/lib/ruby_lsp/requests/on_type_formatting.rb
@@ -162,7 +162,7 @@ def add_edit_with_text(text, position = @position)
 
       #: (Integer line, Integer character) -> void
       def move_cursor_to(line, character)
-        return unless /Visual Studio Code|Cursor|VSCodium/.match?(@client_name)
+        return unless /Visual Studio Code|Cursor|VSCodium|Windsurf/.match?(@client_name)
 
         position = Interface::Position.new(
           line: line,


Test patch:
diff --git a/test/requests/on_type_formatting_test.rb b/test/requests/on_type_formatting_test.rb
index 6ce488f284..44adb0ecaf 100644
--- a/test/requests/on_type_formatting_test.rb
+++ b/test/requests/on_type_formatting_test.rb
@@ -1015,6 +1015,46 @@ def test_includes_snippets_on_vscodium
     assert_equal(expected_edits.to_json, edits.to_json)
   end
 
+  def test_includes_snippets_on_windsurf
+    document = RubyLsp::RubyDocument.new(
+      source: +"",
+      version: 1,
+      uri: URI("file:///fake.rb"),
+      global_state: @global_state,
+    )
+
+    document.push_edits(
+      [{
+        range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } },
+        text: "class Foo",
+      }],
+      version: 2,
+    )
+    document.parse!
+
+    edits = RubyLsp::Requests::OnTypeFormatting.new(
+      document,
+      { line: 1, character: 2 },
+      "\n",
+      "Windsurf",
+    ).perform
+    expected_edits = [
+      {
+        range: { start: { line: 1, character: 2 }, end: { line: 1, character: 2 } },
+        newText: "\n",
+      },
+      {
+        range: { start: { line: 1, character: 2 }, end: { line: 1, character: 2 } },
+        newText: "end",
+      },
+      {
+        range: { start: { line: 1, character: 2 }, end: { line: 1, character: 2 } },
+        newText: "$0",
+      },
+    ]
+    assert_equal(expected_edits.to_json, edits.to_json)
+  end
+
   def test_does_not_confuse_class_parameter_with_keyword
     document = RubyLsp::RubyDocument.new(
       source: +"",