mbeditor 0.12.6 → 0.12.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dffea86be286ee0bdcb216589b12264dcac613a33e59aa2828490706785e1825
4
- data.tar.gz: f786fe449deccc7ff1971c8df15015b7ee0507b45b14a332f83e162b44facc01
3
+ metadata.gz: 7e19be096529fa5f410c792379f88395494fa7783964604b2ff07bb5a93d39d8
4
+ data.tar.gz: d491ca7214661784bbb9d6bc78c13da045a2da9529649d088dae1b0171d17868
5
5
  SHA512:
6
- metadata.gz: b1cf4fcf24d9868b1b5a284c1de2f8eb3b5032a10d8cb3a9ebb1677eed3a6ce9660e658a43f80652ec1a3edb5195aadcf01fc428fc381ed5f62618d59dde4286
7
- data.tar.gz: 3c0c8dc6d5d2aee702d0ed1af2814e884cd204702383dafc21871ffcf2a0b8fa8e871a971d69824ea44f7831f335d3f7e8d0999c832142e5b510d43033ad10cd
6
+ metadata.gz: 99bbd0a1dd19d7191563ea1586f5c38f9b68d0b70319f412be5439fb1588148b135aed1b8a20b66023129065ea84d71880d80054d6500a8ff1f2b83879f941c0
7
+ data.tar.gz: de0e4dfbb928584209ff4d5f1ace4a0bdb5c3b56a20c9a695c3c4770e2ccfd3a5063d9274f450706e13cabf4edd6ce2fdc3313b8477c6cecd28432eabc877246
data/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.12.7] - 2026-08-06
11
+
12
+ ### Fixed
13
+ - **Project search was 10-80x slower than terminal grep on hosts without
14
+ ripgrep.** grep's `--exclude-dir` only accepts plain directory names, so the
15
+ slashed default exclusions (`vendor/bundle`, `public/assets`) never reached
16
+ the command line and grep walked those trees in full on every search, with
17
+ the matches discarded in Ruby afterwards. The grep tier now prunes every
18
+ exclusion with `find` — slashed paths included — and feeds the survivors to
19
+ grep in parallel batches (`xargs -P`), measured 7.2 s → 0.09 s on a 350 MB
20
+ tree with a realistic `vendor/bundle`, 3-4x faster than a plain terminal
21
+ `grep -r`. The same fix applies to `CodeSearchService`, which backs the JS
22
+ definition/global lookups and was reading precompiled `public/assets`
23
+ bundles on every "Cannot find name" the editor reported.
24
+ - **A search query starting with `-` was parsed as grep options.** Queries now
25
+ pass through `-e … --` on the grep tier.
26
+ - **Superseding a search now kills the whole pipeline.** The subprocess is
27
+ spawned in its own process group and the TERM goes to the group, so a
28
+ stacked keystroke cannot leave a find/grep pipeline running behind the
29
+ shell.
30
+ - **`vendor/cache` is excluded from search by default.** `bundle install
31
+ --local` keeps every `.gem` archive there — gigabytes of binary files search
32
+ has no business opening.
33
+
10
34
  ## [0.12.6] - 2026-08-05
11
35
 
12
36
  ### Added
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "shellwords"
4
+ require "etc"
5
+
3
6
  module Mbeditor
4
7
  # Line-oriented regex search over the workspace's JS-family files. Backs the
5
8
  # JS definition/member lookups. Same three-tier backend as
@@ -86,15 +89,26 @@ module Mbeditor
86
89
  lines.map { |l| "#{workspace_root}/#{l}" }
87
90
  end
88
91
 
92
+ # Same shape as SearchReplaceService's grep tier: find prunes every
93
+ # exclusion — slashed ones included, which --exclude-dir cannot express —
94
+ # before the walk, and applies the include/minified globs there too, so
95
+ # grep only ever opens candidate JS files. grep -r --include still walked
96
+ # (and for public/assets, read) the excluded trees on every lookup.
97
+ # ProcessRunner kills the process group, so the sh -c pipeline dies as
98
+ # one unit on timeout.
89
99
  def run_grep(pattern, workspace_root, globs)
90
- includes = globs.map { |g| "--include=#{g}" }
91
- excludes = MINIFIED_GLOBS.map { |g| "--exclude=#{g}" }
92
- args = ["grep", "-I", "-rn", "--color=never", "-E", pattern] + includes + excludes
93
- excluded_paths.reject { |p| p.include?("/") }.select { |d| d.match?(/\A[\w.-]+\z/) }.each do |d|
94
- args << "--exclude-dir=#{d}"
100
+ prune = excluded_paths.flat_map do |p|
101
+ p.include?("/") ? ["-path", File.join(workspace_root, p), "-o"] : ["-name", p, "-o"]
95
102
  end
96
- args << workspace_root
97
- run_command(args, env: { "LC_ALL" => "C" })
103
+ find = ["find", workspace_root]
104
+ find += ["("] + prune[0..-2] + [")", "-prune", "-o"] unless prune.empty?
105
+ find += ["-type", "f", "("] + globs.flat_map { |g| ["-name", g, "-o"] }[0..-2] + [")"]
106
+ MINIFIED_GLOBS.each { |g| find += ["!", "-name", g] }
107
+ find << "-print0"
108
+ xargs = ["xargs", "-0", "-P", Etc.nprocessors.clamp(2, 8).to_s, "-n", "500",
109
+ "grep", "-I", "-Hn", "--line-buffered", "--color=never", "-E", "-e", pattern, "--"]
110
+ cmd = "#{Shellwords.shelljoin(find)} | #{Shellwords.shelljoin(xargs)}"
111
+ run_command(["sh", "-c", cmd], env: { "LC_ALL" => "C" })
98
112
  end
99
113
  end
100
114
  end
@@ -3,6 +3,8 @@
3
3
  require "open3"
4
4
  require "json"
5
5
  require "timeout"
6
+ require "shellwords"
7
+ require "etc"
6
8
 
7
9
  module Mbeditor
8
10
  # Project-wide text search and replace-in-files.
@@ -213,7 +215,9 @@ module Mbeditor
213
215
  timed_out = false
214
216
  timeout_secs = Mbeditor.configuration.search_timeout
215
217
 
216
- io = IO.popen(env, args, err: File::NULL)
218
+ # pgroup: the grep tier is a sh -c pipeline; killing only the shell
219
+ # would leave find/xargs/grep running. TERM goes to the whole group.
220
+ io = IO.popen(env, args, err: File::NULL, pgroup: true)
217
221
  pid = io.pid
218
222
  register_search(root, pid) if supersede
219
223
  watchdog = nil
@@ -301,18 +305,32 @@ module Mbeditor
301
305
  # for -E, and the UTF-8 locale case-folds non-ASCII correctly.
302
306
  [{}, args]
303
307
  else
304
- base_flags = use_regex ? "-E" : "-F"
305
- args = ["grep", "-I", "-Hn", base_flags]
306
- args << "-r" unless paths
307
- args << "-i" unless match_case
308
- args << "-w" if whole_word
309
- # grep --exclude-dir only accepts plain directory names, not paths with
310
- # slashes; slashed patterns are enforced by the ExclusionMatcher
311
- # post-filter instead.
312
- exclusions.reject { |p| p.include?("/") }.select { |d| d.match?(/\A[\w.-]+\z/) }.each { |d| args << "--exclude-dir=#{d}" }
313
- args << query
314
- args += paths ? paths.map { |p| File.expand_path(p, root) } : [root]
315
- [{ "LC_ALL" => "C" }, args]
308
+ grep = ["grep", "-I", "-Hn", "--line-buffered", use_regex ? "-E" : "-F"]
309
+ grep << "-i" unless match_case
310
+ grep << "-w" if whole_word
311
+ # -e + -- so a query starting with "-" can't be parsed as options.
312
+ grep += ["-e", query, "--"]
313
+ if paths
314
+ [{ "LC_ALL" => "C" }, grep + paths.map { |p| File.expand_path(p, root) }]
315
+ else
316
+ # find prunes every exclusion slashed ones included, which
317
+ # --exclude-dir cannot express — *before* the walk, then feeds the
318
+ # survivors to grep in parallel batches. grep -r with post-filtered
319
+ # exclusions read vendor/bundle and public/assets in full on every
320
+ # search; measured 7.2 s -> 0.09 s on a 350 MB tree. --line-buffered
321
+ # keeps concurrent greps from tearing lines mid-row on the shared
322
+ # pipe.
323
+ prune = exclusions.flat_map do |p|
324
+ p.include?("/") ? ["-path", File.join(root, p), "-o"] : ["-name", p, "-o"]
325
+ end
326
+ find = ["find", root]
327
+ find += ["("] + prune[0..-2] + [")", "-prune", "-o"] unless prune.empty?
328
+ find += ["-type", "f", "-print0"]
329
+ workers = Etc.nprocessors.clamp(2, 8)
330
+ xargs = ["xargs", "-0", "-P", workers.to_s, "-n", "500"] + grep
331
+ cmd = "#{Shellwords.shelljoin(find)} | #{Shellwords.shelljoin(xargs)}"
332
+ [{ "LC_ALL" => "C" }, ["sh", "-c", cmd]]
333
+ end
316
334
  end
317
335
  end
318
336
 
@@ -370,9 +388,15 @@ module Mbeditor
370
388
  end
371
389
 
372
390
  def kill_quietly(pid)
373
- Process.kill("TERM", pid) if pid
391
+ return unless pid
392
+
393
+ Process.kill("TERM", -pid)
374
394
  rescue StandardError
375
- nil
395
+ begin
396
+ Process.kill("TERM", pid)
397
+ rescue StandardError
398
+ nil
399
+ end
376
400
  end
377
401
 
378
402
  def monotonic
@@ -18,7 +18,7 @@ module Mbeditor
18
18
  def initialize
19
19
  @allowed_environments = [:development]
20
20
  @workspace_root = nil
21
- @excluded_paths = %w[.git tmp log node_modules .bundle coverage vendor/bundle public/assets storage]
21
+ @excluded_paths = %w[.git tmp log node_modules .bundle coverage vendor/bundle vendor/cache public/assets storage]
22
22
  @rubocop_command = "rubocop"
23
23
  @rubocop_server = true # use rubocop's --server daemon (rubocop >= 1.31) for ~10x faster lint; false forces --no-server
24
24
  @redmine_enabled = false
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mbeditor
4
- VERSION = "0.12.6"
4
+ VERSION = "0.12.7"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mbeditor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.6
4
+ version: 0.12.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Noonan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-05 00:00:00.000000000 Z
11
+ date: 2026-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails