steep 0.49.1 → 0.52.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/Gemfile +4 -1
  4. data/Gemfile.lock +8 -5
  5. data/lib/steep/ast/annotation/collection.rb +10 -8
  6. data/lib/steep/ast/types/factory.rb +5 -5
  7. data/lib/steep/cli.rb +83 -1
  8. data/lib/steep/diagnostic/deprecated/unknown_constant_assigned.rb +28 -0
  9. data/lib/steep/diagnostic/ruby.rb +21 -15
  10. data/lib/steep/drivers/check.rb +1 -0
  11. data/lib/steep/drivers/langserver.rb +2 -2
  12. data/lib/steep/drivers/stats.rb +1 -0
  13. data/lib/steep/drivers/utils/jobs_count.rb +1 -1
  14. data/lib/steep/drivers/watch.rb +1 -1
  15. data/lib/steep/index/source_index.rb +18 -1
  16. data/lib/steep/interface/function.rb +5 -0
  17. data/lib/steep/module_helper.rb +4 -7
  18. data/lib/steep/project.rb +15 -1
  19. data/lib/steep/server/interaction_worker.rb +47 -4
  20. data/lib/steep/server/master.rb +10 -5
  21. data/lib/steep/server/type_check_worker.rb +19 -4
  22. data/lib/steep/server/worker_process.rb +15 -6
  23. data/lib/steep/services/completion_provider.rb +109 -1
  24. data/lib/steep/services/goto_service.rb +23 -10
  25. data/lib/steep/services/hover_content.rb +52 -4
  26. data/lib/steep/services/type_check_service.rb +6 -3
  27. data/lib/steep/source.rb +71 -18
  28. data/lib/steep/type_construction.rb +209 -162
  29. data/lib/steep/type_inference/constant_env.rb +33 -15
  30. data/lib/steep/type_inference/context.rb +6 -7
  31. data/lib/steep/type_inference/type_env.rb +16 -54
  32. data/lib/steep/version.rb +1 -1
  33. data/smoke/const/test_expectations.yml +24 -19
  34. data/smoke/diagnostics/test_expectations.yml +77 -17
  35. data/smoke/integer/test_expectations.yml +24 -4
  36. data/smoke/method/test_expectations.yml +30 -0
  37. data/smoke/regression/test_expectations.yml +24 -0
  38. data/smoke/yield/test_expectations.yml +20 -0
  39. data/steep.gemspec +1 -1
  40. metadata +5 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44df04a21f7dc9d7514ad6acb34549f40fe0b4d962840c29ced4ceca926fd000
4
- data.tar.gz: 15ac298575e8af8d29c1b1ab5e2894f3e62677dedd58b166d147d3d1649026d3
3
+ metadata.gz: f6846cc048d7515ef655b81cf7f399e5852a96c390d90c7f859c07cc6a5c0f94
4
+ data.tar.gz: fecb8b0b0be4e338f3ccf0375064f39167693c0b25b68e3b4ea2f103f117260f
5
5
  SHA512:
6
- metadata.gz: cdc1e1dea1ef2aa9cebd6d40a850894340e2d0ed26f8a982c81c80d384522712c442a499ef12419b0b224cd1bb659944198b8dc897eea692e4d6c06284c86381
7
- data.tar.gz: 22a8d55bd324e3527ffd2a8886bbf7fe28e766ab2dab1e175c43a08b37d256c647f39a05e7890ed5b2726934428bb303c1fe98c45ad07166df2fb2698cc835af
6
+ metadata.gz: 9c08fa692eda8915ef4ce4fe725aecb2a9823c2da188900a3811fdf1d02c6b0369dae8755aed83fe9cc80a376c3309df29efcf849681cd2a9a249559ff6c3ae2
7
+ data.tar.gz: 77953f34eb6ab001dfee4f03e0ec1250f1128fedae9e5b427e0037b97b6b809c5bb8ae4c94d9d93f3af3a702de7dd33a182399fe2b7a81ba8ac0d2708c422579
data/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.52.0 (2022-04-05)
6
+
7
+ * Add `steep binstub` command ([\#527](https://github.com/soutaro/steep/pull/527))
8
+ * Let hover and completion work in heredoc ([\#528](https://github.com/soutaro/steep/pull/528))
9
+ * Better constant typing ([\#529](https://github.com/soutaro/steep/pull/529))
10
+
11
+ ## 0.51.0 (2022-04-01)
12
+
13
+ * Completion for constant ([\#524](https://github.com/soutaro/steep/pull/524))
14
+ * Better hover/completion message ([\#525](https://github.com/soutaro/steep/pull/525))
15
+ * Show available commands when using `--help` ([\#523](https://github.com/soutaro/steep/pull/523))
16
+
17
+ ## 0.50.0 (2022-03-22)
18
+
19
+ * CLI option for override steep command at spawn worker ([\#511](https://github.com/soutaro/steep/pull/511))
20
+ * LSP related improvements for Sublime LSP ([\#513](https://github.com/soutaro/steep/pull/513))
21
+ * Support Windows environment ([\#514](https://github.com/soutaro/steep/pull/514))
22
+ * Let `&:foo` proc work with methods with optional parameters ([\#516](https://github.com/soutaro/steep/pull/516))
23
+ * Fix unexpected error when or-asgn/and-asgn ([\#517](https://github.com/soutaro/steep/pull/517))
24
+ * Fix goto-definition from method call inside block ([\#518](https://github.com/soutaro/steep/pull/518))
25
+ * Better splat in array typing ([\#519](https://github.com/soutaro/steep/pull/519))
26
+
5
27
  ## 0.49.1 (2022-03-11)
6
28
 
7
29
  * Fix lambda typing ([\#506](https://github.com/soutaro/steep/pull/506))
data/Gemfile CHANGED
@@ -9,4 +9,7 @@ gem "without_steep_types", path: "test/gems/without_steep_types"
9
9
  gem "rake"
10
10
  gem "minitest", "~> 5.15"
11
11
  gem "minitest-hooks"
12
- gem "stackprof"
12
+ group :stackprof, optional: true do
13
+ gem "stackprof"
14
+ end
15
+ gem 'minitest-slow_test'
data/Gemfile.lock CHANGED
@@ -1,14 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- steep (0.49.1)
4
+ steep (0.52.0)
5
5
  activesupport (>= 5.1)
6
6
  language_server-protocol (>= 3.15, < 4.0)
7
7
  listen (~> 3.0)
8
8
  parallel (>= 1.0.0)
9
9
  parser (>= 3.0)
10
10
  rainbow (>= 2.2.2, < 4.0)
11
- rbs (>= 2.2.0)
11
+ rbs (>= 2.3.0)
12
12
  terminal-table (>= 2, < 4)
13
13
 
14
14
  PATH
@@ -30,7 +30,7 @@ GEM
30
30
  minitest (>= 5.1)
31
31
  tzinfo (~> 2.0)
32
32
  ast (2.4.2)
33
- concurrent-ruby (1.1.9)
33
+ concurrent-ruby (1.1.10)
34
34
  ffi (1.15.5)
35
35
  i18n (1.10.0)
36
36
  concurrent-ruby (~> 1.0)
@@ -41,7 +41,9 @@ GEM
41
41
  minitest (5.15.0)
42
42
  minitest-hooks (1.5.0)
43
43
  minitest (> 5.3)
44
- parallel (1.21.0)
44
+ minitest-slow_test (0.2.0)
45
+ minitest (>= 5.0)
46
+ parallel (1.22.1)
45
47
  parser (3.1.1.0)
46
48
  ast (~> 2.4.1)
47
49
  rainbow (3.1.1)
@@ -49,7 +51,7 @@ GEM
49
51
  rb-fsevent (0.11.1)
50
52
  rb-inotify (0.10.1)
51
53
  ffi (~> 1.0)
52
- rbs (2.2.2)
54
+ rbs (2.3.1)
53
55
  stackprof (0.2.19)
54
56
  terminal-table (3.0.2)
55
57
  unicode-display_width (>= 1.1.1, < 3)
@@ -63,6 +65,7 @@ PLATFORMS
63
65
  DEPENDENCIES
64
66
  minitest (~> 5.15)
65
67
  minitest-hooks
68
+ minitest-slow_test
66
69
  rake
67
70
  stackprof
68
71
  steep!
@@ -4,7 +4,7 @@ module Steep
4
4
  class Collection
5
5
  attr_reader :annotations
6
6
  attr_reader :factory
7
- attr_reader :current_module
7
+ attr_reader :context
8
8
 
9
9
  attr_reader :var_type_annotations
10
10
  attr_reader :const_type_annotations
@@ -19,10 +19,10 @@ module Steep
19
19
  attr_reader :dynamic_annotations
20
20
  attr_reader :break_type_annotation
21
21
 
22
- def initialize(annotations:, factory:, current_module:)
22
+ def initialize(annotations:, factory:, context:)
23
23
  @annotations = annotations
24
24
  @factory = factory
25
- @current_module = current_module
25
+ @context = context
26
26
 
27
27
  @var_type_annotations = {}
28
28
  @method_type_annotations = {}
@@ -64,7 +64,7 @@ module Steep
64
64
 
65
65
  def absolute_type(type)
66
66
  if type
67
- factory.absolute_type(type, namespace: current_module)
67
+ factory.absolute_type(type, context: context)
68
68
  end
69
69
  end
70
70
 
@@ -140,7 +140,7 @@ module Steep
140
140
  end
141
141
 
142
142
  def merge_block_annotations(annotations)
143
- if annotations.current_module != current_module || annotations.factory != factory
143
+ if annotations.context != context || annotations.factory != factory
144
144
  raise "Cannot merge another annotation: self=#{self}, other=#{annotations}"
145
145
  end
146
146
 
@@ -148,9 +148,11 @@ module Steep
148
148
  annotation.is_a?(BlockType) || annotation.is_a?(BreakType)
149
149
  end
150
150
 
151
- self.class.new(annotations: retained_annotations + annotations.annotations,
152
- factory: factory,
153
- current_module: current_module)
151
+ self.class.new(
152
+ annotations: retained_annotations + annotations.annotations,
153
+ factory: factory,
154
+ context: context
155
+ )
154
156
  end
155
157
 
156
158
  def any?(&block)
@@ -18,7 +18,7 @@ module Steep
18
18
  end
19
19
 
20
20
  def type_name_resolver
21
- @type_name_resolver ||= RBS::TypeNameResolver.from_env(definition_builder.env)
21
+ @type_name_resolver ||= RBS::Resolver::TypeNameResolver.new(definition_builder.env)
22
22
  end
23
23
 
24
24
  def type_opt(type)
@@ -810,15 +810,15 @@ module Steep
810
810
  @env ||= definition_builder.env
811
811
  end
812
812
 
813
- def absolute_type(type, namespace:)
813
+ def absolute_type(type, context:)
814
814
  absolute_type = type_1(type).map_type_name do |name|
815
- absolute_type_name(name, namespace: namespace) || name.absolute!
815
+ absolute_type_name(name, context: context) || name.absolute!
816
816
  end
817
817
  type(absolute_type)
818
818
  end
819
819
 
820
- def absolute_type_name(type_name, namespace:)
821
- type_name_resolver.resolve(type_name, context: namespace.ascend)
820
+ def absolute_type_name(type_name, context:)
821
+ type_name_resolver.resolve(type_name, context: context)
822
822
  end
823
823
 
824
824
  def instance_type(type_name, args: nil, location: nil)
data/lib/steep/cli.rb CHANGED
@@ -18,11 +18,19 @@ module Steep
18
18
  end
19
19
 
20
20
  def self.available_commands
21
- [:init, :check, :validate, :annotations, :version, :project, :watch, :langserver, :stats]
21
+ [:init, :check, :validate, :annotations, :version, :project, :watch, :langserver, :stats, :binstub]
22
22
  end
23
23
 
24
24
  def process_global_options
25
25
  OptionParser.new do |opts|
26
+ opts.banner = <<~USAGE
27
+ Usage: steep [options]
28
+
29
+ available commands: #{CLI.available_commands.join(', ')}
30
+
31
+ Options:
32
+ USAGE
33
+
26
34
  opts.on("--version") do
27
35
  process_version
28
36
  exit 0
@@ -72,6 +80,11 @@ module Steep
72
80
  opts.on("-j N", "--jobs=N", "Specify the number of type check workers (defaults: #{default})") do |count|
73
81
  command.jobs_count = Integer(count) if Integer(count) > 0
74
82
  end
83
+
84
+ command.steep_command = "steep"
85
+ opts.on("--steep-command=COMMAND", "Specify command to exec Steep CLI for worker (defaults: steep)") do |cmd|
86
+ command.steep_command = cmd
87
+ end
75
88
  end
76
89
 
77
90
  def process_init
@@ -195,6 +208,75 @@ module Steep
195
208
  end.run
196
209
  end
197
210
 
211
+ def process_binstub
212
+ path = Pathname("bin/steep")
213
+ force = false
214
+
215
+ OptionParser.new do |opts|
216
+ opts.banner = <<BANNER
217
+ Usage: steep binstub [options]
218
+
219
+ Generate a binstub to execute Steep with setting up Bundler and rbenv/rvm.
220
+ Use the executable for LSP integration setup.
221
+
222
+ Options:
223
+ BANNER
224
+ handle_logging_options opts
225
+
226
+ opts.on("-o PATH", "--output=PATH", "The path of the executable file (defaults to `bin/steep`)") do |v|
227
+ path = Pathname(v)
228
+ end
229
+
230
+ opts.on("--[no-]force", "Overwrite file (defaults to false)") do
231
+ force = true
232
+ end
233
+ end.parse!(argv)
234
+
235
+ path.parent.mkpath
236
+
237
+ gemfile_path =
238
+ if defined?(Bundler)
239
+ Bundler.default_gemfile.relative_path_from(Pathname.pwd + path.parent)
240
+ else
241
+ Pathname("../Gemfile")
242
+ end
243
+
244
+ if path.file?
245
+ if force
246
+ stdout.puts Rainbow("#{path} already exists. Overwriting...").yellow
247
+ else
248
+ stdout.puts Rainbow(''"⚠️ #{path} already exists. Bye! 👋").red
249
+ return 0
250
+ end
251
+ end
252
+
253
+ template = <<TEMPLATE
254
+ #!/usr/bin/env bash
255
+
256
+ BINSTUB_DIR=$(cd $(dirname $0); pwd)
257
+ GEMFILE=${BINSTUB_DIR}/#{gemfile_path}
258
+
259
+ STEEP="bundle exec --gemfile=${GEMFILE} steep"
260
+
261
+ if type "rbenv" > /dev/null 2>&1; then
262
+ STEEP="rbenv exec ${STEEP}"
263
+ else
264
+ if type "rvm" > /dev/null 2>&1; then
265
+ STEEP="rvm ${REPO_ROOT} do ${STEEP}"
266
+ fi
267
+ fi
268
+
269
+ exec $STEEP $@
270
+ TEMPLATE
271
+
272
+ path.write(template)
273
+ path.chmod(0755)
274
+
275
+ stdout.puts Rainbow("Successfully generated executable #{path} 🎉").blue
276
+
277
+ 0
278
+ end
279
+
198
280
  def process_version
199
281
  stdout.puts Steep::VERSION
200
282
  0
@@ -0,0 +1,28 @@
1
+ Steep.logger.error "Diagnostic `Ruby::UnknownConstantAssigned` is deprecated. Use `Ruby::UnknownConstant` instead."
2
+
3
+ module Steep
4
+ module Diagnostic
5
+ module Ruby
6
+ class UnknownConstantAssigned < Base
7
+ attr_reader :context
8
+ attr_reader :name
9
+
10
+ def initialize(node:, context:, name:)
11
+ const = node.children[0]
12
+ loc = if const
13
+ const.loc.expression.join(node.loc.name)
14
+ else
15
+ node.loc.name
16
+ end
17
+ super(node: node, location: loc)
18
+ @context = context
19
+ @name = name
20
+ end
21
+
22
+ def header_line
23
+ "Cannot find the declaration of constant `#{name}`"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -551,27 +551,33 @@ module Steep
551
551
  end
552
552
  end
553
553
 
554
- class UnknownConstantAssigned < Base
555
- attr_reader :context
554
+ class UnknownConstant < Base
556
555
  attr_reader :name
556
+ attr_reader :kind
557
557
 
558
- def initialize(node:, context:, name:)
559
- const = node.children[0]
560
- loc = if const
561
- const.loc.expression.join(node.loc.name)
562
- else
563
- node.loc.name
564
- end
565
- super(node: node, location: loc)
566
- @context = context
558
+ def initialize(node:, name:)
559
+ super(node: node, location: node.loc.name)
567
560
  @name = name
561
+ @kind = :constant
562
+ end
563
+
564
+ def class!
565
+ @kind = :class
566
+ self
567
+ end
568
+
569
+ def module!
570
+ @kind = :module
571
+ self
568
572
  end
569
573
 
570
574
  def header_line
571
- "Cannot find the declaration of constant `#{name}`"
575
+ "Cannot find the declaration of #{kind}: `#{name}`"
572
576
  end
573
577
  end
574
578
 
579
+ autoload :UnknownConstantAssigned, "steep/diagnostic/deprecated/unknown_constant_assigned"
580
+
575
581
  class FallbackAny < Base
576
582
  def initialize(node:)
577
583
  super(node: node)
@@ -744,7 +750,7 @@ module Steep
744
750
  ImplicitBreakValueMismatch => :warning,
745
751
  FallbackAny => :information,
746
752
  ElseOnExhaustiveCase => :warning,
747
- UnknownConstantAssigned => :warning,
753
+ UnknownConstant => :warning,
748
754
  MethodDefinitionMissing => :information
749
755
  }
750
756
  ).freeze
@@ -757,7 +763,7 @@ module Steep
757
763
  ImplicitBreakValueMismatch => nil,
758
764
  FallbackAny => nil,
759
765
  ElseOnExhaustiveCase => nil,
760
- UnknownConstantAssigned => nil,
766
+ UnknownConstant => nil,
761
767
  MethodDefinitionMissing => nil
762
768
  }
763
769
  ).freeze
@@ -770,7 +776,7 @@ module Steep
770
776
  ImplicitBreakValueMismatch => nil,
771
777
  FallbackAny => nil,
772
778
  ElseOnExhaustiveCase => nil,
773
- UnknownConstantAssigned => nil,
779
+ UnknownConstant => nil,
774
780
  MethodDefinitionMissing => nil,
775
781
  UnexpectedJump => nil
776
782
  }
@@ -39,6 +39,7 @@ module Steep
39
39
  steepfile: project.steepfile_path,
40
40
  args: command_line_patterns,
41
41
  delay_shutdown: true,
42
+ steep_command: steep_command,
42
43
  count: jobs_count
43
44
  )
44
45
 
@@ -37,8 +37,8 @@ module Steep
37
37
  def run
38
38
  @project = load_config()
39
39
 
40
- interaction_worker = Server::WorkerProcess.spawn_worker(:interaction, name: "interaction", steepfile: project.steepfile_path)
41
- typecheck_workers = Server::WorkerProcess.spawn_typecheck_workers(steepfile: project.steepfile_path, args: [], count: jobs_count)
40
+ interaction_worker = Server::WorkerProcess.spawn_worker(:interaction, name: "interaction", steepfile: project.steepfile_path, steep_command: steep_command)
41
+ typecheck_workers = Server::WorkerProcess.spawn_typecheck_workers(steepfile: project.steepfile_path, args: [], steep_command: steep_command, count: jobs_count)
42
42
 
43
43
  master = Server::Master.new(
44
44
  project: project,
@@ -129,6 +129,7 @@ module Steep
129
129
  steepfile: project.steepfile_path,
130
130
  delay_shutdown: true,
131
131
  args: command_line_patterns,
132
+ steep_command: steep_command,
132
133
  count: jobs_count
133
134
  )
134
135
 
@@ -2,7 +2,7 @@ module Steep
2
2
  module Drivers
3
3
  module Utils
4
4
  module JobsCount
5
- attr_accessor :jobs_count
5
+ attr_accessor :jobs_count, :steep_command
6
6
  end
7
7
  end
8
8
  end
@@ -41,7 +41,7 @@ module Steep
41
41
  server_reader = LanguageServer::Protocol::Transport::Io::Reader.new(server_read)
42
42
  server_writer = LanguageServer::Protocol::Transport::Io::Writer.new(server_write)
43
43
 
44
- typecheck_workers = Server::WorkerProcess.spawn_typecheck_workers(steepfile: project.steepfile_path, args: dirs.map(&:to_s), count: jobs_count)
44
+ typecheck_workers = Server::WorkerProcess.spawn_typecheck_workers(steepfile: project.steepfile_path, args: dirs.map(&:to_s), steep_command: steep_command, count: jobs_count)
45
45
 
46
46
  master = Server::Master.new(
47
47
  project: project,
@@ -16,7 +16,7 @@ module Steep
16
16
 
17
17
  def add_definition(node)
18
18
  case node.type
19
- when :casgn, :class, :module
19
+ when :casgn, :const
20
20
  @definitions << node
21
21
  else
22
22
  raise "Unexpected constant definition: #{node.type}"
@@ -145,6 +145,23 @@ module Steep
145
145
  raise
146
146
  end
147
147
  end
148
+
149
+ def reference(constant_node: nil)
150
+ case
151
+ when constant_node
152
+ constant_index.each do |name, entry|
153
+ if entry.references.include?(constant_node)
154
+ return name
155
+ end
156
+
157
+ if entry.definitions.include?(constant_node)
158
+ return name
159
+ end
160
+ end
161
+
162
+ nil
163
+ end
164
+ end
148
165
  end
149
166
  end
150
167
  end
@@ -879,6 +879,11 @@ module Steep
879
879
  !has_positional? && !has_keywords?
880
880
  end
881
881
 
882
+ # Returns true if all arguments are non-required.
883
+ def optional?
884
+ required.empty? && required_keywords.empty?
885
+ end
886
+
882
887
  # self + params returns a new params for overloading.
883
888
  #
884
889
  def +(other)
@@ -1,12 +1,9 @@
1
1
  module Steep
2
2
  module ModuleHelper
3
- def module_name_from_node(node)
4
- case node.type
5
- when :const, :casgn
6
- namespace = namespace_from_node(node.children[0]) or return
7
- name = node.children[1]
8
- RBS::TypeName.new(name: name, namespace: namespace)
9
- end
3
+ def module_name_from_node(parent_node, constant_name)
4
+ namespace = namespace_from_node(parent_node) or return
5
+ name = constant_name
6
+ RBS::TypeName.new(name: name, namespace: namespace)
10
7
  end
11
8
 
12
9
  def namespace_from_node(node)
data/lib/steep/project.rb CHANGED
@@ -16,7 +16,21 @@ module Steep
16
16
  steepfile_path.parent
17
17
  end
18
18
 
19
- def relative_path(path)
19
+ def relative_path(orig_path)
20
+ path = if Gem.win_platform?
21
+ path_str = URI.decode_www_form_component(
22
+ orig_path.to_s.delete_prefix("/")
23
+ )
24
+ unless path_str.start_with?(%r{[a-z]:/}i)
25
+ # FIXME: Sometimes drive letter is missing, taking from base_dir
26
+ path_str = base_dir.to_s.split("/")[0] + "/" + path_str
27
+ end
28
+ Pathname.new(
29
+ path_str
30
+ )
31
+ else
32
+ orig_path
33
+ end
20
34
  path.relative_path_from(base_dir)
21
35
  end
22
36
 
@@ -89,7 +89,7 @@ module Steep
89
89
  end
90
90
 
91
91
  LSP::Interface::Hover.new(
92
- contents: { kind: "markdown", value: format_hover(content) },
92
+ contents: { kind: "markdown", value: format_hover(content)&.gsub(/<!--(?~-->)-->/, "") },
93
93
  range: range
94
94
  )
95
95
  end
@@ -175,6 +175,21 @@ HOVER
175
175
  end
176
176
 
177
177
  string
178
+ when Services::HoverContent::ConstantContent
179
+ ss = []
180
+ if content.class_or_module?
181
+ ss << ["```rbs", retrieve_decl_information(content.decl.primary.decl), "```"].join("\n")
182
+ end
183
+
184
+ if content.constant?
185
+ ss << ["```rbs", "#{content.full_name}: #{content.type}", "```"].join("\n")
186
+ end
187
+
188
+ if s = content.comment_string
189
+ ss << s
190
+ end
191
+
192
+ ss.join("\n\n----\n\n")
178
193
  when Services::HoverContent::TypeContent
179
194
  "`#{content.type}`"
180
195
  end
@@ -271,7 +286,7 @@ HOVER
271
286
  ),
272
287
  end: LanguageServer::Protocol::Interface::Position.new(
273
288
  line: job.line - 1,
274
- character: job.column - prefix.size
289
+ character: job.column
275
290
  )
276
291
  )
277
292
 
@@ -327,7 +342,16 @@ HOVER
327
342
  if comment
328
343
  LSP::Interface::MarkupContent.new(
329
344
  kind: LSP::Constant::MarkupKind::MARKDOWN,
330
- value: comment.string
345
+ value: comment.string.gsub(/<!--(?~-->)-->/, "")
346
+ )
347
+ end
348
+ end
349
+
350
+ def format_comments(comments)
351
+ unless comments.empty?
352
+ LSP::Interface::MarkupContent.new(
353
+ kind: LSP::Constant::MarkupKind::MARKDOWN,
354
+ value: comments.map(&:string).join("\n----\n").gsub(/<!--(?~-->)-->/, "")
331
355
  )
332
356
  end
333
357
  end
@@ -408,6 +432,25 @@ HOVER
408
432
  new_text: item.identifier
409
433
  )
410
434
  )
435
+ when Services::CompletionProvider::ConstantItem
436
+ case
437
+ when item.class? || item.module?
438
+ kind = LanguageServer::Protocol::Constant::CompletionItemKind::CLASS
439
+ detail = item.full_name.to_s
440
+ else
441
+ kind = LanguageServer::Protocol::Constant::CompletionItemKind::CONSTANT
442
+ detail = item.type.to_s
443
+ end
444
+ LanguageServer::Protocol::Interface::CompletionItem.new(
445
+ label: item.identifier,
446
+ kind: kind,
447
+ detail: detail,
448
+ documentation: format_comments(item.comments),
449
+ text_edit: LanguageServer::Protocol::Interface::TextEdit.new(
450
+ range: range,
451
+ new_text: item.identifier
452
+ )
453
+ )
411
454
  when Services::CompletionProvider::MethodNameItem
412
455
  method_type_snippet = method_type_to_snippet(item.method_type)
413
456
  LanguageServer::Protocol::Interface::CompletionItem.new(
@@ -418,7 +461,7 @@ HOVER
418
461
  new_text: "#{item.identifier}#{method_type_snippet}",
419
462
  range: range
420
463
  ),
421
- documentation: item.comment&.string,
464
+ documentation: format_comment(item.comment),
422
465
  insert_text_format: LanguageServer::Protocol::Constant::InsertTextFormat::SNIPPET,
423
466
  sort_text: item.inherited? ? 'z' : 'a' # Ensure language server puts non-inherited methods before inherited methods
424
467
  )
@@ -49,9 +49,14 @@ module Steep
49
49
  end
50
50
 
51
51
  def checking_path?(path)
52
- library_paths.include?(path) ||
53
- signature_paths.include?(path) ||
54
- code_paths.include?(path)
52
+ [library_paths, signature_paths, code_paths].any? do |paths|
53
+ if Gem.win_platform?
54
+ # FIXME: Sometimes drive letter is missing, so comparing without drive letter.
55
+ paths.any? {|p| p.to_s.split("/", 2)[1] == path.to_s.split("/", 2)[1]}
56
+ else
57
+ paths.include?(path)
58
+ end
59
+ end
55
60
  end
56
61
 
57
62
  def checked(path)
@@ -496,7 +501,7 @@ module Steep
496
501
  partialResult: true
497
502
  },
498
503
  completion_provider: LSP::Interface::CompletionOptions.new(
499
- trigger_characters: [".", "@"],
504
+ trigger_characters: [".", "@", ":"],
500
505
  work_done_progress: true
501
506
  ),
502
507
  workspace_symbol_provider: true,
@@ -739,7 +744,7 @@ module Steep
739
744
  { kind: "end" }
740
745
  else
741
746
  progress_string = ("▮"*(percentage/5)) + ("▯"*(20 - percentage/5))
742
- { kind: "report", percentage: percentage, message: "#{progress_string} (#{percentage}%)" }
747
+ { kind: "report", percentage: percentage, message: "#{progress_string}" }
743
748
  end
744
749
 
745
750
  job_queue << SendMessageJob.to_client(