diff --git a/jekyll/editors.markdown b/jekyll/editors.markdown index 5b774c525..0a9f8aa44 100644 --- a/jekyll/editors.markdown +++ b/jekyll/editors.markdown @@ -78,6 +78,9 @@ configuration languages (JSON, Lua, ELisp, etc.). "inlayHint": { "implicitHashValue": true, "implicitRescue": true + }, + "onTypeFormatting": { + "commentContinuation": true } }, "indexing": { diff --git a/jekyll/index.markdown b/jekyll/index.markdown index 5c8a57fc3..6ecf02ef3 100644 --- a/jekyll/index.markdown +++ b/jekyll/index.markdown @@ -329,6 +329,18 @@ On type formatting applies changes to the code as the user is typing. For exampl {: .note } In VS Code, format on type is disabled by default. You can enable it with `"editor.formatOnType": true` +By default, breaking a line inside a comment continues the comment on the next line. VS Code users can turn this +behavior off with the following setting: + +```jsonc +{ + // Disable comment continuation when breaking lines inside a comment (defaults to true) + "rubyLsp.featuresConfiguration.onTypeFormatting.commentContinuation": false +} +``` + +To configure other editors, see the [initialization options](editors#all-initialization-options). + diff --git a/lib/ruby_lsp/global_state.rb b/lib/ruby_lsp/global_state.rb index 82e6edbcf..ba374f4bc 100644 --- a/lib/ruby_lsp/global_state.rb +++ b/lib/ruby_lsp/global_state.rb @@ -81,6 +81,10 @@ def initialize enableAll: false, enableTestCodeLens: true, }), + onTypeFormatting: RequestConfig.new({ + enableAll: false, + commentContinuation: true, + }), } #: Hash[Symbol, RequestConfig] end diff --git a/lib/ruby_lsp/requests/on_type_formatting.rb b/lib/ruby_lsp/requests/on_type_formatting.rb index da4904e19..e70d84bc3 100644 --- a/lib/ruby_lsp/requests/on_type_formatting.rb +++ b/lib/ruby_lsp/requests/on_type_formatting.rb @@ -23,8 +23,8 @@ def provider /.*\s\bdo\b($|\s)/, ] #: Array[Regexp] - #: (RubyDocument document, Hash[Symbol, untyped] position, String trigger_character, String client_name) -> void - def initialize(document, position, trigger_character, client_name) + #: (RubyDocument document, Hash[Symbol, untyped] position, String trigger_character, String client_name, ?RequestConfig? config) -> void + def initialize(document, position, trigger_character, client_name, config = nil) super() @document = document @lines = @document.source.lines #: Array[String] @@ -36,6 +36,7 @@ def initialize(document, position, trigger_character, client_name) @edits = [] #: Array[Interface::TextEdit] @trigger_character = trigger_character @client_name = client_name + @config = config end # @override @@ -51,9 +52,11 @@ def perform # But if it's a RBS signature starting with `#:`, we'll ignore it # so users can immediately continue typing the method definition if (comment_match = @previous_line.match(/^#(?!:)(\s*)/)) - handle_comment_line( - comment_match[1], #: as !nil - ) + if comment_continuation_enabled? + handle_comment_line( + comment_match[1], #: as !nil + ) + end elsif @document.syntax_error? match = /(<<((-|~)?))(?['"`]?)(?\w+)\k/.match(@previous_line) heredoc_delimiter = match && match.named_captures["delimiter"] @@ -73,6 +76,14 @@ def perform private + #: -> bool + def comment_continuation_enabled? + config = @config + return true unless config + + config.enabled?(:commentContinuation) || false + end + #: -> void def handle_pipe current_line = @lines[@position[:line]] diff --git a/lib/ruby_lsp/server.rb b/lib/ruby_lsp/server.rb index c700fc6c5..082cce76b 100644 --- a/lib/ruby_lsp/server.rb +++ b/lib/ruby_lsp/server.rb @@ -715,6 +715,7 @@ def text_document_on_type_formatting(message) params[:position], params[:ch], @store.client_name, + @global_state.feature_configuration(:onTypeFormatting), ).perform, ), ) diff --git a/test/global_state_test.rb b/test/global_state_test.rb index 35f96e754..4e33b574b 100644 --- a/test/global_state_test.rb +++ b/test/global_state_test.rb @@ -357,6 +357,24 @@ def test_feature_configuration_with_partially_provided_configuration assert(inlay_hint_config.enabled?(:implicitHashValue)) end + def test_on_type_formatting_comment_continuation_configuration + state = GlobalState.new + on_type_formatting_config = state.feature_configuration(:onTypeFormatting) #: as !nil + assert(on_type_formatting_config.enabled?(:commentContinuation)) + + state.apply_options({ + initializationOptions: { + featuresConfiguration: { + onTypeFormatting: { + commentContinuation: false, + }, + }, + }, + }) + + refute(on_type_formatting_config.enabled?(:commentContinuation)) + end + def test_initialize_features_with_enable_all_configuration state = GlobalState.new state.apply_options({ diff --git a/test/requests/on_type_formatting_test.rb b/test/requests/on_type_formatting_test.rb index 69a75a9fe..e29068f2e 100644 --- a/test/requests/on_type_formatting_test.rb +++ b/test/requests/on_type_formatting_test.rb @@ -292,6 +292,76 @@ def test_comment_continuation assert_equal(expected_edits.to_json, edits.to_json) end + def test_comment_continuation_applies_with_default_configuration + 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: " # something", + }], + version: 2, + ) + document.parse! + + edits = RubyLsp::Requests::OnTypeFormatting.new( + document, + { line: 0, character: 14 }, + "\n", + "Visual Studio Code", + @global_state.feature_configuration(:onTypeFormatting), + ).perform + expected_edits = [ + { + range: { start: { line: 0, character: 14 }, end: { line: 0, character: 14 } }, + newText: "# ", + }, + ] + assert_equal(expected_edits.to_json, edits.to_json) + end + + def test_comment_continuation_can_be_disabled_through_configuration + @global_state.apply_options({ + initializationOptions: { + featuresConfiguration: { + onTypeFormatting: { + commentContinuation: false, + }, + }, + }, + }) + + 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: " # something", + }], + version: 2, + ) + document.parse! + + edits = RubyLsp::Requests::OnTypeFormatting.new( + document, + { line: 0, character: 14 }, + "\n", + "Visual Studio Code", + @global_state.feature_configuration(:onTypeFormatting), + ).perform + assert_empty(edits) + end + def test_comment_continuation_does_not_apply_to_rbs_signatures document = RubyLsp::RubyDocument.new( source: +"", diff --git a/test/server_test.rb b/test/server_test.rb index 1301c8b59..2b71b548e 100644 --- a/test/server_test.rb +++ b/test/server_test.rb @@ -853,6 +853,47 @@ def foo end end + def test_on_type_formatting_comment_continuation_can_be_disabled + uri = URI::Generic.from_path(path: "/fake.rb") + + capture_io do + @server.process_message(id: 1, method: "initialize", params: { + initializationOptions: { + featuresConfiguration: { + onTypeFormatting: { + commentContinuation: false, + }, + }, + }, + }) + + @server.process_message({ + method: "textDocument/didOpen", + params: { + textDocument: { + uri: uri, + text: "# some comment", + version: 1, + languageId: "ruby", + }, + }, + }) + + @server.process_message({ + id: 2, + method: "textDocument/onTypeFormatting", + params: { + textDocument: { uri: uri }, + position: { line: 1, character: 0 }, + ch: "\n", + }, + }) + + result = find_message(RubyLsp::Result, id: 2) + assert_empty(result.response) + end + end + def test_show_window_responses_are_redirected_to_addons klass = Class.new(RubyLsp::Addon) do def activate(global_state, outgoing_queue) diff --git a/vscode/package.json b/vscode/package.json index aafb95c37..51098c930 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -368,6 +368,20 @@ "default": true } } + }, + "onTypeFormatting": { + "description": "Customize on type formatting features", + "type": "object", + "properties": { + "enableAll": { + "type": "boolean" + }, + "commentContinuation": { + "description": "Automatically continue comments when breaking lines inside a comment", + "type": "boolean", + "default": true + } + } } } },