ffi-clang 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/ffi/clang/args/args.rb +209 -0
  4. data/lib/ffi/clang/args/darwin.rb +57 -0
  5. data/lib/ffi/clang/args/linux.rb +33 -0
  6. data/lib/ffi/clang/args/mingw.rb +21 -0
  7. data/lib/ffi/clang/args/mswin.rb +195 -0
  8. data/lib/ffi/clang/code_completion.rb +64 -14
  9. data/lib/ffi/clang/comment.rb +11 -5
  10. data/lib/ffi/clang/compilation_database.rb +19 -14
  11. data/lib/ffi/clang/cursor.rb +293 -38
  12. data/lib/ffi/clang/cursor_set.rb +38 -0
  13. data/lib/ffi/clang/diagnostic.rb +7 -11
  14. data/lib/ffi/clang/diagnostic_set.rb +52 -0
  15. data/lib/ffi/clang/evaluation.rb +61 -0
  16. data/lib/ffi/clang/file.rb +59 -0
  17. data/lib/ffi/clang/index.rb +249 -34
  18. data/lib/ffi/clang/index_action.rb +552 -0
  19. data/lib/ffi/clang/invocation_support.rb +44 -0
  20. data/lib/ffi/clang/lib/code_completion.rb +10 -4
  21. data/lib/ffi/clang/lib/comment.rb +3 -1
  22. data/lib/ffi/clang/lib/compilation_database.rb +6 -6
  23. data/lib/ffi/clang/lib/cursor.rb +159 -30
  24. data/lib/ffi/clang/lib/cursor_set.rb +19 -0
  25. data/lib/ffi/clang/lib/evaluation.rb +32 -0
  26. data/lib/ffi/clang/lib/file.rb +6 -1
  27. data/lib/ffi/clang/lib/inclusions.rb +4 -1
  28. data/lib/ffi/clang/lib/index.rb +122 -13
  29. data/lib/ffi/clang/lib/indexing.rb +319 -0
  30. data/lib/ffi/clang/lib/printing_policy.rb +35 -28
  31. data/lib/ffi/clang/lib/source_location.rb +6 -1
  32. data/lib/ffi/clang/lib/source_range.rb +14 -1
  33. data/lib/ffi/clang/lib/string.rb +11 -0
  34. data/lib/ffi/clang/lib/token.rb +3 -1
  35. data/lib/ffi/clang/lib/translation_unit.rb +12 -1
  36. data/lib/ffi/clang/lib/type.rb +41 -3
  37. data/lib/ffi/clang/lib.rb +15 -43
  38. data/lib/ffi/clang/overridden_cursors.rb +67 -0
  39. data/lib/ffi/clang/platform.rb +6 -3
  40. data/lib/ffi/clang/source_location.rb +19 -4
  41. data/lib/ffi/clang/source_range.rb +28 -2
  42. data/lib/ffi/clang/string_set.rb +55 -0
  43. data/lib/ffi/clang/token.rb +50 -16
  44. data/lib/ffi/clang/translation_unit.rb +71 -15
  45. data/lib/ffi/clang/types/pointer.rb +23 -15
  46. data/lib/ffi/clang/types/type.rb +129 -2
  47. data/lib/ffi/clang/version.rb +2 -1
  48. data/lib/ffi/clang.rb +8 -1
  49. data/license.md +1 -1
  50. data/readme.md +101 -8
  51. data/releases.md +164 -1
  52. data.tar.gz.sig +0 -0
  53. metadata +22 -7
  54. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8e7e863a657523fadf6b3bbe7bf5e53fc047fb028418464d9969011041ced1f7
4
- data.tar.gz: 9fa425cacdfeda2905d5450081d308e7b37979f96beb181c5dcc64e51976c33e
3
+ metadata.gz: f49d102b63151e047ed3158af59e2ab793ea3a422f13a8a83515305f7c8f5025
4
+ data.tar.gz: df66a932286bb412577e176757d79ceea6b82dc2859407cc5abcb720595f57ff
5
5
  SHA512:
6
- metadata.gz: 7200e81a683eed2b5415d544025af4c800767b96ac145f28b602c2d84c933650ddde92a8310b897592a7b434f08ccaf647a62965ce933493af7b5332fe6aac95
7
- data.tar.gz: 494b4856f2dd7fbbabe4638bd3efda40910163d59cc78203f68553994791356f920ebf72962e8b1ce3270c6df3ed46c5e90cfaec13dbdbac87bc1e2d4b3f0323
6
+ metadata.gz: 00a0370a903ff0d26d60f3b17dee7a3d94cfaabbf836db4cfb8eba7a56ae966f86a95ab426ef7de1a475b7cb00dd77e2e65ca3553be2d16d05e7e2c13ee4cafa
7
+ data.tar.gz: 3efc05ec2a7da83578cff2bdb963f20dc5ff02168f471cf417672aac0715abf570fbc9744fb696a065a4dbd5f63a453fef7af09d6467a1342d392cddb58d2074
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,209 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Charlie Savage.
5
+
6
+ require "open3"
7
+
8
+ module FFI
9
+ module Clang
10
+ # Platform-specific clang configuration: finding libclang, locating the
11
+ # resource directory, and injecting extra command-line arguments into
12
+ # parse_translation_unit.
13
+ #
14
+ # All discovery is lazy — nothing runs until a method is called.
15
+ class Args
16
+ # Set the loaded libclang path after ffi_lib succeeds,
17
+ # so resource dir probing can use it.
18
+ # @parameter path [String | Nil] Path to the loaded libclang library.
19
+ attr_writer :libclang_loaded_path
20
+
21
+ # Factory: returns the platform-appropriate subclass instance.
22
+ # @returns [Args] A platform-specific instance.
23
+ def self.create
24
+ case FFI::Clang.platform
25
+ when :darwin
26
+ DarwinArgs.new
27
+ when :mingw
28
+ MingwArgs.new
29
+ when :mswin
30
+ MswinArgs.new
31
+ else
32
+ LinuxArgs.new
33
+ end
34
+ end
35
+
36
+ # Ordered list of library paths for ffi_lib.
37
+ # @returns [Array(String)] Paths to try when loading libclang.
38
+ def libclang_paths
39
+ @libclang_paths ||= if ENV["LIBCLANG"]
40
+ [ENV["LIBCLANG"]]
41
+ else
42
+ find_libclang_paths
43
+ end
44
+ end
45
+
46
+ # Extra args to inject into parse_translation_unit.
47
+ # Includes -resource-dir (unless already present) plus any
48
+ # platform-specific flags.
49
+ # @parameter command_line_args [Array(String)] The existing command line arguments.
50
+ # @returns [Array(String)] Additional args to append.
51
+ def command_line_args(command_line_args = [])
52
+ args = []
53
+
54
+ if !command_line_args.include?("-resource-dir") && resource_dir
55
+ args.push("-resource-dir", resource_dir)
56
+ end
57
+
58
+ args.concat(extra_args(command_line_args))
59
+ args
60
+ end
61
+
62
+ # The resolved resource directory path.
63
+ # @returns [String | Nil] The resource directory path, or nil if not found.
64
+ def resource_dir
65
+ if defined?(@resource_dir)
66
+ @resource_dir
67
+ else
68
+ @resource_dir = find_resource_dir
69
+ end
70
+ end
71
+
72
+ # Called after ffi_lib successfully loads libclang.
73
+ # Subclasses may override to perform post-load setup.
74
+ #
75
+ # @parameter library [FFI::DynamicLibrary] The loaded libclang library.
76
+ def post_load(library)
77
+ end
78
+
79
+ private
80
+
81
+ def versions
82
+ 22.downto(17)
83
+ end
84
+
85
+ # Platform-specific extra args beyond -resource-dir.
86
+ # Subclasses override as needed.
87
+ # @parameter command_line_args [Array(String)] The existing command line arguments.
88
+ # @returns [Array(String)] Additional platform-specific args.
89
+ def extra_args(command_line_args)
90
+ []
91
+ end
92
+
93
+ # --- Shared helpers ---
94
+
95
+ # Find the llvm-config binary. Checks LLVM_CONFIG env, then PATH
96
+ # (unless LLVM_VERSION is set to pin a specific version).
97
+ # @returns [String | Nil] Path to llvm-config, or nil.
98
+ def llvm_config
99
+ if defined?(@llvm_config)
100
+ return @llvm_config
101
+ end
102
+
103
+ @llvm_config = ENV["LLVM_CONFIG"]
104
+
105
+ unless @llvm_config || ENV["LLVM_VERSION"]
106
+ @llvm_config = "llvm-config"
107
+ end
108
+
109
+ @llvm_config
110
+ end
111
+
112
+ # Query llvm-config for its library directory.
113
+ # @returns [String | Nil] The library directory, or nil.
114
+ def llvm_library_dir
115
+ return nil unless llvm_config
116
+
117
+ @llvm_library_dir ||= run_command(llvm_config, "--libdir")
118
+ end
119
+
120
+ # Query llvm-config for its binary directory.
121
+ # @returns [String | Nil] The binary directory, or nil.
122
+ def llvm_bin_dir
123
+ return nil unless llvm_config
124
+
125
+ @llvm_bin_dir ||= run_command(llvm_config, "--bindir")
126
+ end
127
+
128
+ # Ask a clang binary for its resource directory.
129
+ # @parameter clang [String] Path to or name of the clang binary.
130
+ # @returns [String | Nil] The resource directory path, or nil.
131
+ def resource_dir_from_clang(clang)
132
+ dir = run_command(clang, "-print-resource-dir")
133
+ valid_resource_dir?(dir) ? dir : nil
134
+ end
135
+
136
+ def run_command(*command)
137
+ stdout, _stderr, status = Open3.capture3(*command)
138
+ return nil unless status.success?
139
+
140
+ output = stdout.strip
141
+ output.empty? ? nil : output
142
+ rescue Errno::ENOENT
143
+ nil
144
+ end
145
+
146
+ # Probe for the resource directory relative to the libclang shared library.
147
+ # @parameter libclang_path [String] Path to the libclang shared library.
148
+ # @returns [String | Nil] The resource directory path, or nil.
149
+ def probe_from_libclang(libclang_path)
150
+ base = ::File.expand_path(::File.dirname(libclang_path))
151
+
152
+ candidates = []
153
+ candidates.concat Dir.glob(::File.join(base, "..", "lib", "clang", "*"))
154
+ candidates.concat Dir.glob(::File.join(base, "..", "..", "lib", "clang", "*"))
155
+ candidates.concat Dir.glob(::File.join(base, "clang", "*"))
156
+
157
+ candidates = candidates.map{|p| ::File.expand_path(p)}.uniq
158
+
159
+ candidates
160
+ .select{|dir| valid_resource_dir?(dir)}
161
+ .sort
162
+ .last
163
+ end
164
+
165
+ # Check whether a directory looks like a valid clang resource directory.
166
+ # @parameter dir [String | Nil] The directory to check.
167
+ # @returns [Boolean] True if the directory contains expected compiler headers.
168
+ def valid_resource_dir?(dir)
169
+ return false unless dir && ::File.directory?(dir)
170
+
171
+ inc = ::File.join(dir, "include")
172
+ return false unless ::File.directory?(inc)
173
+
174
+ ::File.exist?(::File.join(inc, "stddef.h")) ||
175
+ ::File.exist?(::File.join(inc, "__stddef_size_t.h")) ||
176
+ ::File.exist?(::File.join(inc, "stdint.h"))
177
+ end
178
+
179
+ # Common resource dir search: env override, clang from llvm-config,
180
+ # clang on PATH, probe from loaded libclang.
181
+ # @returns [String | Nil] The resource directory path, or nil.
182
+ def find_resource_dir
183
+ # 1. Explicit override via environment variable.
184
+ env = ENV["LIBCLANG_RESOURCE_DIR"]
185
+ return env if valid_resource_dir?(env)
186
+
187
+ # 2. Clang binary from llvm-config.
188
+ if (bin_dir = llvm_bin_dir)
189
+ clang_path = ::File.join(bin_dir, "clang")
190
+ if (dir = resource_dir_from_clang(clang_path))
191
+ return dir
192
+ end
193
+ end
194
+
195
+ # 3. clang on PATH.
196
+ if (dir = resource_dir_from_clang("clang"))
197
+ return dir
198
+ end
199
+
200
+ # 4. Probe relative to the loaded libclang shared library.
201
+ if @libclang_loaded_path && (dir = probe_from_libclang(@libclang_loaded_path))
202
+ return dir
203
+ end
204
+
205
+ nil
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Charlie Savage.
5
+
6
+ module FFI
7
+ module Clang
8
+ # macOS-specific clang configuration using Xcode toolchain paths.
9
+ class DarwinArgs < Args
10
+ private
11
+
12
+ def find_libclang_paths
13
+ if llvm_library_dir
14
+ return [::File.join(llvm_library_dir, "libclang.dylib")]
15
+ end
16
+
17
+ # Try xcode-select paths.
18
+ begin
19
+ xcode_dir = `xcode-select -p`.chomp
20
+ %W[
21
+ #{xcode_dir}/Toolchains/XcodeDefault.xctoolchain/usr/lib/libclang.dylib
22
+ #{xcode_dir}/usr/lib/libclang.dylib
23
+ ].each do |f|
24
+ return [f] if ::File.exist?(f)
25
+ end
26
+ rescue Errno::ENOENT
27
+ # xcode-select not available
28
+ end
29
+
30
+ ["clang"]
31
+ end
32
+
33
+ def isysroot
34
+ if defined?(@isysroot)
35
+ return @isysroot
36
+ end
37
+
38
+ @isysroot = begin
39
+ stdout, _stderr, status = Open3.capture3("xcrun", "--show-sdk-path")
40
+ status.success? ? stdout.strip : nil
41
+ rescue Errno::ENOENT
42
+ nil
43
+ end
44
+ end
45
+
46
+ def extra_args(command_line_args)
47
+ args = []
48
+
49
+ if !command_line_args.include?("-isysroot") && isysroot
50
+ args.push("-isysroot", isysroot)
51
+ end
52
+
53
+ args
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Charlie Savage.
5
+
6
+ module FFI
7
+ module Clang
8
+ # Linux-specific clang configuration.
9
+ class LinuxArgs < Args
10
+ private
11
+
12
+ def find_libclang_paths
13
+ prefix = llvm_library_dir
14
+
15
+ paths = versions.flat_map do |version|
16
+ 5.downto(0).map do |minor|
17
+ libclang_path(prefix, "libclang.so.#{version}.#{minor}")
18
+ end
19
+ end
20
+
21
+ paths.concat(versions.map do |version|
22
+ libclang_path(prefix, "libclang.so.#{version}")
23
+ end)
24
+
25
+ paths << libclang_path(prefix, "libclang.so")
26
+ end
27
+
28
+ def libclang_path(prefix, name)
29
+ prefix ? ::File.join(prefix, name) : name
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Charlie Savage.
5
+
6
+ module FFI
7
+ module Clang
8
+ # MinGW/MSYS2-specific clang configuration.
9
+ class MingwArgs < Args
10
+ private
11
+
12
+ def find_libclang_paths
13
+ if llvm_bin_dir
14
+ return [::File.join(llvm_bin_dir, "libclang.dll")]
15
+ end
16
+
17
+ ["libclang.dll"]
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Charlie Savage.
5
+
6
+ module FFI
7
+ module Clang
8
+ # MSVC-specific clang configuration. Discovers libclang and system
9
+ # include paths from the Visual Studio installation:
10
+ #
11
+ # 1. Find the VS installation path via vswhere.exe
12
+ # 2. Call vcvarsall.bat to set up the MSVC developer environment
13
+ # 3. Run clang-cl -v -E -x c++ NUL in that environment
14
+ # 4. Parse the "#include <...> search starts here:" block from the output
15
+ # 5. Inject each discovered path as -I into parse_translation_unit
16
+ class MswinArgs < Args
17
+ VSWHERE = "C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
18
+
19
+ # Pin libclang in memory so Windows will not unload it at exit.
20
+ #
21
+ # LLVM's rpmalloc allocator registers Fiber Local Storage (FLS)
22
+ # callbacks via FlsAlloc but does not call FlsFree on
23
+ # DLL_PROCESS_DETACH (LLVM bug #154361, fixed in LLVM 22.1.0 by
24
+ # https://github.com/llvm/llvm-project/pull/171465).
25
+ # Pinning with GET_MODULE_HANDLE_EX_FLAG_PIN prevents the unload
26
+ # so the FLS callbacks remain valid through process shutdown.
27
+ #
28
+ # @parameter library [FFI::DynamicLibrary] The loaded libclang library.
29
+ def post_load(library)
30
+ symbol = library.find_symbol("clang_getClangVersion")
31
+ return unless symbol
32
+
33
+ kernel32 = FFI::DynamicLibrary.open("kernel32", 0)
34
+ get_module_handle_ex_w = kernel32.find_function("GetModuleHandleExW")
35
+ return unless get_module_handle_ex_w
36
+
37
+ get_module_handle_ex_flag_from_address = 0x4
38
+ get_module_handle_ex_flag_pin = 0x1
39
+ flags = get_module_handle_ex_flag_from_address | get_module_handle_ex_flag_pin
40
+ handle_out = FFI::MemoryPointer.new(:pointer)
41
+ pin = FFI::Function.new(:bool, [:uint, :pointer, :pointer], get_module_handle_ex_w)
42
+ pin.call(flags, symbol, handle_out)
43
+ end
44
+
45
+ private
46
+
47
+
48
+ def find_libclang_paths
49
+ if llvm_bin_dir
50
+ return [::File.join(llvm_bin_dir, "libclang.dll")]
51
+ end
52
+
53
+ if (vs_llvm = vs_llvm_dir)
54
+ return [::File.join(vs_llvm, "bin", "libclang.dll")]
55
+ end
56
+
57
+ ["libclang.dll"]
58
+ end
59
+
60
+ # Mswin skips "clang on PATH" — uses clang-cl probe instead.
61
+ def find_resource_dir
62
+ # 1. Explicit override via environment variable.
63
+ env = ENV["LIBCLANG_RESOURCE_DIR"]
64
+ return env if valid_resource_dir?(env)
65
+
66
+ # 2. clang-cl next to the loaded libclang.
67
+ if @libclang_loaded_path
68
+ clang_cl = ::File.join(::File.dirname(@libclang_loaded_path), "clang-cl.exe")
69
+ if ::File.exist?(clang_cl) && (dir = resource_dir_from_clang(clang_cl))
70
+ return dir
71
+ end
72
+ end
73
+
74
+ # 3. Probe relative to the loaded libclang shared library.
75
+ if @libclang_loaded_path && (dir = probe_from_libclang(@libclang_loaded_path))
76
+ return dir
77
+ end
78
+
79
+ nil
80
+ end
81
+
82
+ def extra_args(command_line_args)
83
+ args = []
84
+
85
+ system_includes.each do |path|
86
+ unless command_line_args.include?(path)
87
+ args.push("-I", path)
88
+ end
89
+ end
90
+
91
+ args
92
+ end
93
+
94
+ # Parse system include paths from clang-cl running in a VS developer environment.
95
+ # @returns [Array(String)] System include directories.
96
+ def system_includes
97
+ @system_includes ||= find_system_includes
98
+ end
99
+
100
+ def find_system_includes
101
+ vs_path = vs_installation_path
102
+ return [] unless vs_path
103
+
104
+ vcvarsall = ::File.join(vs_path, "VC", "Auxiliary", "Build", "vcvarsall.bat")
105
+ return [] unless ::File.exist?(vcvarsall)
106
+
107
+ clang_cl = find_clang_cl
108
+ return [] unless clang_cl
109
+
110
+ arch = RbConfig::CONFIG["target_cpu"] == "x64" ? "x64" : "arm64"
111
+ cmd = "cmd /c \"call \"#{vcvarsall}\" #{arch} >nul 2>&1 && \"#{clang_cl}\" -v -E -x c++ NUL 2>&1\""
112
+
113
+ stdout, _status = Open3.capture2(cmd)
114
+ parse_include_paths(stdout)
115
+ rescue Errno::ENOENT
116
+ []
117
+ end
118
+
119
+ # Find clang-cl.exe — next to loaded libclang, or in VS LLVM dir.
120
+ # @returns [String | Nil] Path to clang-cl.exe, or nil.
121
+ def find_clang_cl
122
+ if @libclang_loaded_path
123
+ path = ::File.join(::File.dirname(@libclang_loaded_path), "clang-cl.exe")
124
+ return path if ::File.exist?(path)
125
+ end
126
+
127
+ if (vs_llvm = vs_llvm_dir)
128
+ path = ::File.join(vs_llvm, "bin", "clang-cl.exe")
129
+ return path if ::File.exist?(path)
130
+ end
131
+
132
+ nil
133
+ end
134
+
135
+ # Parse the #include <...> search paths from clang -v output.
136
+ # @parameter output [String] The combined stdout/stderr from clang-cl -v.
137
+ # @returns [Array(String)] The include directories.
138
+ def parse_include_paths(output)
139
+ paths = []
140
+ in_search_list = false
141
+
142
+ output.each_line do |line|
143
+ line = line.strip
144
+
145
+ if line == "#include <...> search starts here:"
146
+ in_search_list = true
147
+ elsif line == "End of search list."
148
+ break
149
+ elsif in_search_list && !line.empty?
150
+ paths << line
151
+ end
152
+ end
153
+
154
+ paths
155
+ end
156
+
157
+ # Find the VS installation path using vswhere.
158
+ # @returns [String | Nil] Path like "C:/Program Files/Microsoft Visual Studio/18/Insiders", or nil.
159
+ def vs_installation_path
160
+ if defined?(@vs_installation_path)
161
+ return @vs_installation_path
162
+ end
163
+
164
+ @vs_installation_path = find_vs_installation_path
165
+ end
166
+
167
+ def find_vs_installation_path
168
+ return nil unless ::File.exist?(VSWHERE)
169
+
170
+ stdout, _stderr, status = Open3.capture3(
171
+ VSWHERE, "-latest", "-products", "*", "-prerelease",
172
+ "-requires", "Microsoft.VisualStudio.Component.VC.Llvm.Clang",
173
+ "-property", "installationPath"
174
+ )
175
+ return nil unless status.success?
176
+
177
+ path = stdout.strip
178
+ path.empty? ? nil : path
179
+ rescue Errno::ENOENT
180
+ nil
181
+ end
182
+
183
+ # Find the VS-bundled LLVM directory.
184
+ # @returns [String | Nil] Path like ".../VC/Tools/Llvm/x64", or nil.
185
+ def vs_llvm_dir
186
+ vs_path = vs_installation_path
187
+ return nil unless vs_path
188
+
189
+ arch = RbConfig::CONFIG["target_cpu"] == "x64" ? "x64" : "ARM64"
190
+ llvm_dir = ::File.join(vs_path, "VC", "Tools", "Llvm", arch)
191
+ ::File.directory?(llvm_dir) ? llvm_dir : nil
192
+ end
193
+ end
194
+ end
195
+ end
@@ -3,7 +3,7 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2014, by Masahiro Sano.
5
5
  # Copyright, 2014-2025, by Samuel Williams.
6
- # Copyright, 2023-2024, by Charlie Savage.
6
+ # Copyright, 2023-2026, by Charlie Savage.
7
7
 
8
8
  require_relative "lib/code_completion"
9
9
 
@@ -21,12 +21,19 @@ module FFI
21
21
  class Results < FFI::AutoPointer
22
22
  include Enumerable
23
23
 
24
- # @attribute [Integer] The number of completion results.
24
+ # @attribute [r] size
25
+ # @returns [Integer] The number of completion results.
25
26
  attr_reader :size
26
27
 
27
- # @attribute [Array(Result)] The array of completion results.
28
+ # @attribute [r] results
29
+ # @returns [Array(Result)] The array of completion results.
28
30
  attr_reader :results
29
31
 
32
+ # @attribute [r] code_complete_results
33
+ # @returns [Lib::CXCodeCompleteResults] The underlying results structure.
34
+ # @private
35
+ attr_reader :code_complete_results
36
+
30
37
  # Initialize code completion results.
31
38
  # @parameter code_complete_results [Lib::CXCodeCompleteResults] The completion results structure.
32
39
  # @parameter translation_unit [TranslationUnit] The parent translation unit.
@@ -47,10 +54,11 @@ module FFI
47
54
  # Iterate over each completion result.
48
55
  # @yields {|result| ...} Each completion result.
49
56
  # @parameter result [Result] The completion result.
57
+ # @returns [Enumerator] If no block is given.
50
58
  def each(&block)
51
- @results.each do |token|
52
- block.call(token)
53
- end
59
+ return to_enum(__method__) unless block_given?
60
+
61
+ @results.each(&block)
54
62
  end
55
63
 
56
64
  # Get the number of diagnostics.
@@ -69,9 +77,9 @@ module FFI
69
77
  # Get all diagnostics.
70
78
  # @returns [Array(Diagnostic)] Array of diagnostics.
71
79
  def diagnostics
72
- num_diagnostics.times.map {|i|
80
+ num_diagnostics.times.map do |i|
73
81
  Diagnostic.new(@translation_unit, Lib.get_code_complete_get_diagnostic(@code_complete_results, i))
74
- }
82
+ end
75
83
  end
76
84
 
77
85
  # Get the completion contexts.
@@ -126,9 +134,11 @@ module FFI
126
134
  @size = @code_complete_results[:num]
127
135
  cur_ptr = @code_complete_results[:results]
128
136
  @results = []
129
- @size.times {@results << Result.new(Lib::CXCompletionResult.new(cur_ptr))
130
- cur_ptr += Lib::CXCompletionResult.size
131
- }
137
+ @size.times do |i|
138
+ completion_result = Lib::CXCompletionResult.new(cur_ptr)
139
+ @results << Result.new(completion_result, self, i)
140
+ cur_ptr += Lib::CXCompletionResult.size
141
+ end
132
142
  end
133
143
  end
134
144
 
@@ -136,8 +146,12 @@ module FFI
136
146
  class Result
137
147
  # Initialize a completion result.
138
148
  # @parameter result [Lib::CXCompletionResult] The completion result structure.
139
- def initialize(result)
149
+ # @parameter owner [Results | Nil] The owning Results object (prevents GC of the parent allocation).
150
+ # @parameter index [Integer | Nil] The index of this result in the parent.
151
+ def initialize(result, owner = nil, index = nil)
140
152
  @result = result
153
+ @owner = owner
154
+ @index = index
141
155
  end
142
156
 
143
157
  # Get the kind of completion.
@@ -152,6 +166,25 @@ module FFI
152
166
  CodeCompletion::String.new @result[:string]
153
167
  end
154
168
 
169
+ # Get the number of fix-its required before this completion can be applied.
170
+ # @returns [Integer] The number of fix-its.
171
+ def num_fix_its
172
+ return 0 unless @owner
173
+
174
+ Lib.get_completion_num_fix_its(@owner.code_complete_results, @index)
175
+ end
176
+
177
+ # Get all fix-its for this completion result.
178
+ # @returns [Array(FixIt)] The fix-its.
179
+ def fix_its
180
+ num_fix_its.times.map do |i|
181
+ range_ptr = MemoryPointer.new(Lib::CXSourceRange, 1)
182
+ text = Lib.extract_string(Lib.get_completion_fix_it(@owner.code_complete_results, @index, i, range_ptr))
183
+ range = SourceRange.new(Lib::CXSourceRange.new(range_ptr))
184
+ FixIt.new(text, range)
185
+ end
186
+ end
187
+
155
188
  # Get a string representation of this result.
156
189
  # @returns [String] The result as a string.
157
190
  def inspect
@@ -159,6 +192,23 @@ module FFI
159
192
  end
160
193
  end
161
194
 
195
+ # Represents a fix-it that must be applied before a completion can be inserted.
196
+ class FixIt
197
+ # @attribute [r] text
198
+ # @returns [String] The replacement text.
199
+ # @attribute [r] range
200
+ # @returns [SourceRange] The source range to replace.
201
+ attr_reader :text, :range
202
+
203
+ # Initialize a fix-it.
204
+ # @parameter text [String] The replacement text.
205
+ # @parameter range [SourceRange] The source range to replace.
206
+ def initialize(text, range)
207
+ @text = text
208
+ @range = range
209
+ end
210
+ end
211
+
162
212
  # Represents a code completion string with chunks.
163
213
  class String
164
214
  # Initialize a completion string.
@@ -197,7 +247,7 @@ module FFI
197
247
  # Get all chunks as an array of hashes.
198
248
  # @returns [Array(Hash)] Array of chunk hashes with `:kind`, `:text`, and `:completion` keys.
199
249
  def chunks
200
- num_chunks.times.map {|i|
250
+ num_chunks.times.map{|i|
201
251
  { kind: chunk_kind(i), text: chunk_text(i), completion: chunk_completion(i) }
202
252
  }
203
253
  end
@@ -230,7 +280,7 @@ module FFI
230
280
  # Get all annotations.
231
281
  # @returns [Array(String)] Array of annotation strings.
232
282
  def annotations
233
- num_annotations.times.map {|i|
283
+ num_annotations.times.map{|i|
234
284
  Lib.extract_string Lib.get_completion_annotation(@pointer, i)
235
285
  }
236
286
  end