canopus 0.1.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 (146) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +148 -0
  5. data/docs/adr/001-runtime-gem-components.md +24 -0
  6. data/docs/adr/002-bounded-diff-engine.md +22 -0
  7. data/docs/adr/003-background-language-analysis.md +22 -0
  8. data/docs/adr/004-persistent-display-map.md +23 -0
  9. data/docs/adr/005-windows-runtime.md +27 -0
  10. data/docs/adr/README.md +36 -0
  11. data/docs/distribution.md +27 -0
  12. data/docs/lsp.md +38 -0
  13. data/docs/performance.md +40 -0
  14. data/docs/snippets.md +32 -0
  15. data/docs/vim.md +24 -0
  16. data/docs/workspace_edits.md +32 -0
  17. data/examples/native_smoke.rb +25 -0
  18. data/examples/plugins/word_count.rb +11 -0
  19. data/exe/canopus +19 -0
  20. data/lib/canopus/block_map.rb +19 -0
  21. data/lib/canopus/buffer.rb +293 -0
  22. data/lib/canopus/cli.rb +171 -0
  23. data/lib/canopus/controller.rb +507 -0
  24. data/lib/canopus/data_compat.rb +25 -0
  25. data/lib/canopus/display_map/line_builder.rb +54 -0
  26. data/lib/canopus/display_map/line_set.rb +8 -0
  27. data/lib/canopus/display_map/pending_line_set.rb +9 -0
  28. data/lib/canopus/display_map/summary.rb +23 -0
  29. data/lib/canopus/display_map/worker.rb +89 -0
  30. data/lib/canopus/display_map.rb +350 -0
  31. data/lib/canopus/display_point.rb +5 -0
  32. data/lib/canopus/editor/snippet_expandable.rb +281 -0
  33. data/lib/canopus/editor.rb +445 -0
  34. data/lib/canopus/error.rb +5 -0
  35. data/lib/canopus/fold_map.rb +87 -0
  36. data/lib/canopus/git/blame.rb +50 -0
  37. data/lib/canopus/git/commit.rb +7 -0
  38. data/lib/canopus/git/corrupt_object.rb +7 -0
  39. data/lib/canopus/git/diff.rb +131 -0
  40. data/lib/canopus/git/index.rb +94 -0
  41. data/lib/canopus/git/object_database.rb +45 -0
  42. data/lib/canopus/git/pack.rb +186 -0
  43. data/lib/canopus/git/repository.rb +313 -0
  44. data/lib/canopus/git/status.rb +74 -0
  45. data/lib/canopus/git/tree_entry.rb +7 -0
  46. data/lib/canopus/git.rb +15 -0
  47. data/lib/canopus/icon_theme.rb +43 -0
  48. data/lib/canopus/language/background_analysis/job.rb +12 -0
  49. data/lib/canopus/language/background_analysis/scheduler.rb +70 -0
  50. data/lib/canopus/language/background_analysis.rb +280 -0
  51. data/lib/canopus/language/definition.rb +7 -0
  52. data/lib/canopus/language/document.rb +143 -0
  53. data/lib/canopus/language/symbol.rb +7 -0
  54. data/lib/canopus/language/syntax_worker.rb +91 -0
  55. data/lib/canopus/language.rb +42 -0
  56. data/lib/canopus/lazy_rope.rb +218 -0
  57. data/lib/canopus/lsp/client.rb +299 -0
  58. data/lib/canopus/lsp/error.rb +7 -0
  59. data/lib/canopus/lsp/future/subscription.rb +9 -0
  60. data/lib/canopus/lsp/future.rb +87 -0
  61. data/lib/canopus/lsp/protocol.rb +83 -0
  62. data/lib/canopus/lsp/server_error.rb +13 -0
  63. data/lib/canopus/lsp/timeout.rb +7 -0
  64. data/lib/canopus/lsp/transport.rb +123 -0
  65. data/lib/canopus/lsp.rb +19 -0
  66. data/lib/canopus/markdown.rb +69 -0
  67. data/lib/canopus/match_data_compat.rb +17 -0
  68. data/lib/canopus/multi_buffer.rb +266 -0
  69. data/lib/canopus/pane.rb +61 -0
  70. data/lib/canopus/patch/composite.rb +12 -0
  71. data/lib/canopus/patch/reload.rb +18 -0
  72. data/lib/canopus/patch.rb +27 -0
  73. data/lib/canopus/performance_recorder.rb +207 -0
  74. data/lib/canopus/plugins/api.rb +50 -0
  75. data/lib/canopus/plugins/isolated_runtime.rb +167 -0
  76. data/lib/canopus/plugins/local_runtime.rb +25 -0
  77. data/lib/canopus/plugins/permission_denied.rb +7 -0
  78. data/lib/canopus/plugins/registry.rb +30 -0
  79. data/lib/canopus/plugins.rb +11 -0
  80. data/lib/canopus/project/ignore_matcher.rb +104 -0
  81. data/lib/canopus/project/search.rb +164 -0
  82. data/lib/canopus/project/search_worker/cancelled.rb +3 -0
  83. data/lib/canopus/project/search_worker/runner.rb +9 -0
  84. data/lib/canopus/project/search_worker.rb +108 -0
  85. data/lib/canopus/project/tree.rb +48 -0
  86. data/lib/canopus/project/watcher.rb +59 -0
  87. data/lib/canopus/project.rb +127 -0
  88. data/lib/canopus/regexp_compat.rb +30 -0
  89. data/lib/canopus/save_conflict.rb +5 -0
  90. data/lib/canopus/selection.rb +11 -0
  91. data/lib/canopus/settings.rb +118 -0
  92. data/lib/canopus/snippet/transform.rb +209 -0
  93. data/lib/canopus/snippet.rb +183 -0
  94. data/lib/canopus/tab_map.rb +30 -0
  95. data/lib/canopus/terminal/cell.rb +7 -0
  96. data/lib/canopus/terminal/grid.rb +321 -0
  97. data/lib/canopus/terminal/pty.rb +103 -0
  98. data/lib/canopus/terminal/scrollback.rb +38 -0
  99. data/lib/canopus/terminal/vt.rb +398 -0
  100. data/lib/canopus/terminal.rb +13 -0
  101. data/lib/canopus/theme.rb +43 -0
  102. data/lib/canopus/version.rb +5 -0
  103. data/lib/canopus/vim/commandable.rb +148 -0
  104. data/lib/canopus/vim/motionable.rb +321 -0
  105. data/lib/canopus/vim/operator_capable.rb +377 -0
  106. data/lib/canopus/vim/text_object_selectable.rb +141 -0
  107. data/lib/canopus/vim.rb +426 -0
  108. data/lib/canopus/workspace/edit/executable.rb +139 -0
  109. data/lib/canopus/workspace/edit/node.rb +8 -0
  110. data/lib/canopus/workspace/edit/plan.rb +170 -0
  111. data/lib/canopus/workspace/edit/resource_preparable.rb +98 -0
  112. data/lib/canopus/workspace/edit.rb +6 -0
  113. data/lib/canopus/workspace/file_change_aware.rb +40 -0
  114. data/lib/canopus/workspace/file_previewable.rb +31 -0
  115. data/lib/canopus/workspace/git_aware.rb +140 -0
  116. data/lib/canopus/workspace/language_aware.rb +413 -0
  117. data/lib/canopus/workspace/language_server_configurable.rb +181 -0
  118. data/lib/canopus/workspace/project_searchable.rb +208 -0
  119. data/lib/canopus/workspace/project_tree_editable.rb +82 -0
  120. data/lib/canopus/workspace/session_persistable.rb +153 -0
  121. data/lib/canopus/workspace/settings_aware.rb +91 -0
  122. data/lib/canopus/workspace/view/terminal_presentable.rb +185 -0
  123. data/lib/canopus/workspace/view.rb +643 -0
  124. data/lib/canopus/workspace.rb +525 -0
  125. data/lib/canopus/wrap_map.rb +108 -0
  126. data/lib/canopus.rb +24 -0
  127. data/sig/canopus.rbs +375 -0
  128. data/sig/controller.rbs +55 -0
  129. data/sig/display_stages.rbs +49 -0
  130. data/sig/git.rbs +140 -0
  131. data/sig/lsp.rbs +99 -0
  132. data/sig/markdown.rbs +11 -0
  133. data/sig/performance_recorder.rbs +23 -0
  134. data/sig/search_services.rbs +8 -0
  135. data/sig/services.rbs +81 -0
  136. data/sig/snippet.rbs +42 -0
  137. data/sig/terminal.rbs +125 -0
  138. data/sig/vim.rbs +27 -0
  139. data/sig/workspace_edits.rbs +11 -0
  140. data/sig/workspace_services.rbs +117 -0
  141. data/tools/certify_snippet_regex.rb +35 -0
  142. data/tools/check_dependencies.rb +80 -0
  143. data/tools/native_check.rb +70 -0
  144. data/tools/package.rb +113 -0
  145. data/tools/package_test.rb +32 -0
  146. metadata +314 -0
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Shared packaging gate. Optional argument: a standalone installed-gem smoke.
4
+ require "tmpdir"
5
+ require "open3"
6
+ require "rubygems/package"
7
+ require "rbconfig"
8
+ require "json"
9
+
10
+ root = File.expand_path("..", __dir__)
11
+ entry = File.basename(root)
12
+ smoke = ARGV.first && File.expand_path(ARGV.fetch(0))
13
+ abort "Usage: ruby tools/check_dependencies.rb [smoke.rb]" if ARGV.length > 1 || (smoke && !File.file?(smoke))
14
+ specification = Gem::Specification.load(File.join(root, "#{entry}.gemspec"))
15
+ dependencies = specification.runtime_dependencies.map(&:name).sort
16
+ expected = %w[alhena antares denebola kochab prism rouge spica unicode-display_width zaniah]
17
+ abort "unexpected runtime gem dependencies" unless dependencies == expected
18
+ abort "native extension declaration" unless specification.extensions.empty?
19
+ abort "packaged native binary" if specification.files.any? { |path| path.match?(/\.(?:so|bundle|dll|dylib|a|o)\z/i) }
20
+ abort "packaged vendor directory" if specification.files.any? { |path| path.start_with?("vendor/") }
21
+ %w[LICENSE.txt CHANGELOG.md].each do |path|
22
+ abort "missing packaged #{path}" unless specification.files.include?(path)
23
+ end
24
+ specification.files.grep(/\.(?:ttf|otf|ttc)\z/i).each do |font|
25
+ directory = File.dirname(font)
26
+ abort "font license not packaged: #{font}" unless specification.files.any? { |path| File.dirname(path) == directory && File.basename(path).match?(/(?:OFL|LICENSE|COPYING)/i) }
27
+ end
28
+ Dir.mktmpdir("#{entry}-install-") do |directory|
29
+ artifact = File.join(directory, "#{entry}.gem")
30
+ Dir.chdir(root) { Gem::Package.build(specification, false, false, artifact) }
31
+ installation = File.join(directory, "gems")
32
+ gem_command = File.join(RbConfig::CONFIG.fetch("bindir"), "gem")
33
+ environment = ENV.each_key.grep(/\ABUNDLE/).to_h { |key| [key, nil] }.merge(
34
+ "GEM_HOME" => installation, "GEM_PATH" => ([installation] + Gem.path).join(File::PATH_SEPARATOR),
35
+ "RUBYLIB" => nil, "RUBYOPT" => nil
36
+ )
37
+ output, status = Open3.capture2e(environment, Gem.ruby, gem_command, "install", "--local", "--ignore-dependencies", "--no-document", "--install-dir", installation, artifact, chdir: directory)
38
+ abort output unless status.success?
39
+ installed = File.join(installation, "gems", "#{specification.name}-#{specification.version}")
40
+ script = <<~RUBY
41
+ require "prism"
42
+ require #{entry.inspect}
43
+ actual = $LOADED_FEATURES.find { |path| path.end_with?(#{"/lib/#{entry}.rb".inspect}) }
44
+ raise "loaded outside clean installation: " + actual.to_s unless actual && File.realpath(actual).start_with?(#{File.realpath(installed).inspect} + File::SEPARATOR)
45
+ load #{smoke.inspect} if #{!smoke.nil?}
46
+ raise "headless/default providers imported Fiddle" if defined?(Fiddle)
47
+ missing = #{dependencies.inspect} - Gem.loaded_specs.keys
48
+ raise "runtime dependencies not loaded: " + missing.join(", ") unless missing.empty?
49
+ source = #{File.join(root, "lib").inspect} + File::SEPARATOR
50
+ leaked = $LOADED_FEATURES.select { |path| path.start_with?(source) }
51
+ raise "workspace library leaked into installed runtime: " + leaked.join(", ") unless leaked.empty?
52
+ puts "isolated install: #{entry} #{specification.version}, runtime dependencies: #{dependencies.join(', ')}, native bridge imports 0"
53
+ RUBY
54
+ output, status = Open3.capture2e(environment, Gem.ruby, "-e", script, chdir: directory)
55
+ abort output unless status.success?
56
+ puts output
57
+
58
+ # Exercise the installed executable too: version covers its YJIT re-exec,
59
+ # then headless rendering covers argument parsing, profiling and cleanup.
60
+ executable = File.join(installed, "exe", entry)
61
+ output, status = Open3.capture2e(environment, Gem.ruby, executable, "--version", chdir: directory)
62
+ abort output unless status.success? && output.strip == specification.version.to_s
63
+ png = File.join(directory, "frame.png")
64
+ profile = File.join(directory, "profile.json")
65
+ launch = <<~RUBY
66
+ at_exit do
67
+ raise "installed headless CLI imported Fiddle" if defined?(Fiddle)
68
+ raise "YJIT re-exec marker leaked into application environment" if ENV.key?("CANOPUS_YJIT_REEXEC")
69
+ end
70
+ load #{executable.inspect}
71
+ RUBY
72
+ jit_flags = Gem.win_platform? ? [] : ["--yjit"]
73
+ output, status = Open3.capture2e(environment, Gem.ruby, *jit_flags, "-e", launch, "--",
74
+ "--headless", png, "--size", "320x180", "--project", directory, "--profile", profile, chdir: directory)
75
+ abort output unless status.success?
76
+ abort "installed CLI did not render a PNG" unless File.binread(png, 8) == "\x89PNG\r\n\x1a\n".b
77
+ report = JSON.parse(File.read(profile))
78
+ abort "installed CLI recorded no completed frames" unless report.fetch("statistics").fetch("frames").positive?
79
+ puts "isolated CLI: RubyGems, version/#{jit_flags.empty? ? 'interpreter' : 'YJIT'}/headless/profile passed"
80
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Runs against released gems, without sibling load paths.
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
+ require "canopus"
6
+ require "canopus/cli"
7
+ require "tmpdir"
8
+
9
+ Dir.mktmpdir("canopus-native-") do |directory|
10
+ File.write(File.join(directory, "example.rb"), "# Native editor test\nputs :hello\n")
11
+ workspace = Canopus::Workspace.new(root: directory)
12
+ workspace.open("example.rb")
13
+ platform = RUBY_PLATFORM.include?("darwin") ? :mac : RUBY_PLATFORM.match?(/mingw|mswin/) ? :windows : :linux
14
+ window = Zaniah::Platform.open_window(backend: platform, width: 640, height: 400, title: "Canopus integration check")
15
+ window.text_system = Zaniah::TextSystem::Renderer.new
16
+ controller = Canopus::Controller.new(workspace, window)
17
+ workspace.start_watching
18
+ controller.tick
19
+ if platform == :linux && window.is_a?(Zaniah::Platform::Linux::Window)
20
+ sender = Process.spawn("xdotool", "windowfocus", window.handle.to_s, "key", "z")
21
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 2
22
+ until workspace.editor.buffer.text.start_with?("z")
23
+ raise "native keyboard input did not reach the editor" if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
24
+ controller.tick
25
+ sleep 0.01
26
+ end
27
+ Process.wait(sender)
28
+ end
29
+ workspace.split(:horizontal)
30
+ # Idle/PNG checks start only after visible background work has settled.
31
+ Canopus.send(:settle_export, controller, window)
32
+ raise "split panes not rendered" unless controller.view.editor_bounds.length == 2
33
+ output = ARGV.find { |argument| argument.end_with?(".png") }
34
+ window.write_png(output) if output
35
+ raise "native GPU draw missing" unless window.device.draw_calls.positive?
36
+ puts "#{window.class}: split editor rendered with #{window.device.draw_calls} GPU draws"
37
+ if ARGV.include?("--idle")
38
+ previous_tick = window.instance_variable_get(:@on_tick)
39
+ warmup = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 1
40
+ deadline = warmup + 5
41
+ start_time = cpu = elapsed = percent = nil
42
+ frames = inputs = 0
43
+ previous_draw = window.instance_variable_get(:@draw)
44
+ previous_input = window.instance_variable_get(:@on_input)
45
+ window.draw { |surface| frames += 1 if start_time; previous_draw.call(surface) }
46
+ window.on_input { |event| inputs += 1 if start_time; previous_input.call(event) }
47
+ window.on_close { true }
48
+ window.on_tick do
49
+ previous_tick&.call
50
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
51
+ if !start_time && now >= warmup
52
+ start_time, cpu = now, Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
53
+ elsif now >= deadline
54
+ elapsed = now - start_time
55
+ percent = (Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID) - cpu) / elapsed * 100
56
+ window.close
57
+ end
58
+ end
59
+ window.run
60
+ puts "#{inputs.zero? ? 'Idle' : 'Input-contaminated'} editor CPU: #{percent.round(3)}% of one core over #{elapsed.round(2)}s (watcher enabled; #{frames} frames, #{inputs} input events)"
61
+ raise "idle check received input; repeat without interacting with the test window" if ENV["CANOPUS_IDLE_LIMIT"] && inputs.positive?
62
+ if ENV["CANOPUS_IDLE_LIMIT"] && percent > Float(ENV["CANOPUS_IDLE_LIMIT"])
63
+ raise "idle CPU exceeds CANOPUS_IDLE_LIMIT"
64
+ end
65
+ end
66
+ ensure
67
+ workspace&.close
68
+ window&.on_close { true }
69
+ window&.close
70
+ end
data/tools/package.rb ADDED
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "optparse"
5
+ require "rbconfig"
6
+ require "cgi"
7
+ require "json"
8
+ require "shellwords"
9
+
10
+ module CanopusPackage
11
+ module_function
12
+
13
+ # Packages the source distribution; never downloads or bundles Ruby or gems.
14
+ def build(platform:, output:, ruby: nil, source: File.expand_path("..", __dir__))
15
+ raise ArgumentError, "platform must be mac, linux, or windows" unless %w[mac linux windows].include?(platform)
16
+ output, source = File.expand_path(output), File.realpath(source)
17
+ raise ArgumentError, "output cannot contain control characters" if output.match?(/[\x00-\x1f\x7f]/)
18
+ raise ArgumentError, "output must not already exist" if File.exist?(output) || File.symlink?(output)
19
+ raise ArgumentError, "output cannot be inside the source tree" if output.start_with?(source + File::SEPARATOR)
20
+ raise ArgumentError, "mac output must end in .app" if platform == "mac" && !output.end_with?(".app")
21
+ ruby ||= platform == "windows" ? "ruby.exe" : RbConfig.ruby
22
+ raise ArgumentError, "Ruby executable cannot contain quotes or control characters" if ruby.empty? || ruby.match?(/["\x00-\x1f\x7f]/)
23
+ version = File.read(File.join(source, "lib", "canopus", "version.rb"))[/VERSION\s*=\s*["']([^"']+)/, 1]
24
+ raise ArgumentError, "cannot read Canopus version" unless version
25
+ entries = %w[lib exe assets LICENSE.txt README.md CHANGELOG.md].filter_map do |name|
26
+ path = File.join(source, name)
27
+ path if File.exist?(path)
28
+ end
29
+ entries.each do |path|
30
+ candidates = [path, *Dir.glob(File.join(path, "**", "*"), File::FNM_DOTMATCH)]
31
+ raise ArgumentError, "source distribution contains symlinks" if candidates.any? { |candidate| File.symlink?(candidate) }
32
+ end
33
+ runtime = File.join(output, platform == "mac" ? "Contents/Resources/canopus" : "share/canopus")
34
+ FileUtils.mkdir_p(runtime)
35
+ entries.each { |entry| FileUtils.cp_r(entry, runtime, preserve: true) }
36
+ case platform
37
+ when "mac"
38
+ executable = File.join(output, "Contents", "MacOS", "canopus")
39
+ shell_launcher(executable, ruby, "../Resources/canopus/exe/canopus")
40
+ File.write(File.join(output, "Contents", "Info.plist"), <<~PLIST)
41
+ <?xml version="1.0" encoding="UTF-8"?>
42
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
43
+ <plist version="1.0"><dict>
44
+ <key>CFBundleName</key><string>Canopus</string>
45
+ <key>CFBundleDisplayName</key><string>Canopus</string>
46
+ <key>CFBundleExecutable</key><string>canopus</string>
47
+ <key>CFBundleIdentifier</key><string>org.rubifex.canopus</string>
48
+ <key>CFBundlePackageType</key><string>APPL</string>
49
+ <key>CFBundleShortVersionString</key><string>#{CGI.escapeHTML(version)}</string>
50
+ <key>CFBundleVersion</key><string>#{CGI.escapeHTML(version)}</string>
51
+ <key>NSHighResolutionCapable</key><true/>
52
+ <key>NSSupportsAutomaticGraphicsSwitching</key><true/>
53
+ </dict></plist>
54
+ PLIST
55
+ when "linux"
56
+ executable = File.join(output, "bin", "canopus")
57
+ shell_launcher(executable, ruby, "../share/canopus/exe/canopus")
58
+ desktop = File.join(output, "share", "applications", "canopus.desktop")
59
+ FileUtils.mkdir_p(File.dirname(desktop))
60
+ quoted = executable.gsub(/[\\"`$%]/) { |character| character == "%" ? "%%" : character == "\\" ? "\\" * 4 : "\\" * 2 + character }
61
+ File.write(desktop, <<~DESKTOP)
62
+ [Desktop Entry]
63
+ Type=Application
64
+ Name=Canopus
65
+ Comment=Pure Ruby text editor
66
+ Exec="#{quoted}" %F
67
+ Terminal=false
68
+ Categories=Development;TextEditor;
69
+ MimeType=text/plain;
70
+ StartupNotify=true
71
+ DESKTOP
72
+ when "windows"
73
+ executable = File.join(output, "canopus.cmd")
74
+ File.write(executable, "@echo off\r\nsetlocal DisableDelayedExpansion\r\n\"#{ruby.gsub('%', '%%')}\" \"%~dp0share\\canopus\\exe\\canopus\" %*\r\nexit /b %errorlevel%\r\n")
75
+ File.write(File.join(output, "Create-Shortcut.ps1"), <<~POWERSHELL.gsub("\n", "\r\n"))
76
+ param([string]$Destination = [Environment]::GetFolderPath('Desktop'))
77
+ $ErrorActionPreference = 'Stop'
78
+ $shell = New-Object -ComObject WScript.Shell
79
+ $shortcut = $shell.CreateShortcut((Join-Path $Destination 'Canopus.lnk'))
80
+ $shortcut.TargetPath = Join-Path $PSScriptRoot 'canopus.cmd'
81
+ $shortcut.WorkingDirectory = [Environment]::GetFolderPath('UserProfile')
82
+ $shortcut.Description = 'Canopus text editor (requires CRuby 3.1+)'
83
+ $shortcut.Save()
84
+ POWERSHELL
85
+ end
86
+ File.write(File.join(output, "package.json"), JSON.pretty_generate({name: "canopus", version: version, platform: platform, ruby: ruby, bundled_ruby: false}) + "\n")
87
+ executable
88
+ end
89
+
90
+ def shell_launcher(path, ruby, relative)
91
+ FileUtils.mkdir_p(File.dirname(path))
92
+ File.write(path, <<~SH)
93
+ #!/bin/sh
94
+ set -eu
95
+ here=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
96
+ exec #{Shellwords.escape(ruby)} --yjit "$here/#{relative}" "$@"
97
+ SH
98
+ File.chmod(0o755, path)
99
+ end
100
+ end
101
+
102
+ if $PROGRAM_NAME == __FILE__
103
+ options = {}
104
+ parser = OptionParser.new do |flags|
105
+ flags.banner = "Usage: ruby tools/package.rb --platform mac|linux|windows --output PATH [--ruby PATH]"
106
+ flags.on("--platform NAME") { |value| options[:platform] = value }
107
+ flags.on("--output PATH") { |value| options[:output] = value }
108
+ flags.on("--ruby PATH", "System Ruby executable; no runtime is bundled") { |value| options[:ruby] = value }
109
+ end
110
+ parser.parse!
111
+ abort parser.to_s unless options[:platform] && options[:output]
112
+ puts CanopusPackage.build(**options)
113
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tmpdir"
4
+ require "open3"
5
+ require_relative "package"
6
+
7
+ Dir.mktmpdir("canopus-package-") do |directory|
8
+ %w[mac linux windows].each do |platform|
9
+ output = File.join(directory, platform == "mac" ? "Canopus Test.app" : "Canopus #{platform}")
10
+ launcher = CanopusPackage.build(platform: platform, output: output)
11
+ metadata = JSON.parse(File.read(File.join(output, "package.json")))
12
+ raise "runtime must not be bundled" unless metadata["bundled_ruby"] == false
13
+ if platform == "windows"
14
+ raise "missing shortcut installer" unless File.file?(File.join(output, "Create-Shortcut.ps1"))
15
+ raise "unsafe launcher argument forwarding" unless File.read(launcher).include?("%*")
16
+ raise "Windows launcher requires unsupported YJIT" if File.read(launcher).include?("--yjit")
17
+ next
18
+ end
19
+ output_text, status = Open3.capture2e(launcher, "--version")
20
+ raise "packaged #{platform} executable failed: #{output_text}" unless status.success? && output_text.strip == metadata["version"]
21
+ png = File.join(directory, "#{platform}.png")
22
+ output_text, status = Open3.capture2e(launcher, "--headless", png, "--size", "320x200", "--project", directory)
23
+ raise "packaged render failed: #{output_text}" unless status.success? && File.binread(png, 8) == "\x89PNG\r\n\x1a\n".b
24
+ begin
25
+ CanopusPackage.build(platform: platform, output: File.dirname(launcher))
26
+ raise "existing output accepted"
27
+ rescue ArgumentError
28
+ # Existing packages are never overwritten implicitly.
29
+ end
30
+ end
31
+ end
32
+ puts "Package layouts, launchers, and headless rendering passed (Windows runtime not executed)."
metadata ADDED
@@ -0,0 +1,314 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: canopus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: alhena
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.1.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 0.1.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: antares
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.1.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.1.0
40
+ - !ruby/object:Gem::Dependency
41
+ name: denebola
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.1.0
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 0.1.0
54
+ - !ruby/object:Gem::Dependency
55
+ name: kochab
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.1.0
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.1.0
68
+ - !ruby/object:Gem::Dependency
69
+ name: prism
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rouge
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '5.0'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '5.0'
96
+ - !ruby/object:Gem::Dependency
97
+ name: spica
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 0.1.0
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 0.1.0
110
+ - !ruby/object:Gem::Dependency
111
+ name: unicode-display_width
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '3.2'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '3.2'
124
+ - !ruby/object:Gem::Dependency
125
+ name: zaniah
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 0.1.0
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: 0.1.0
138
+ email:
139
+ - t.yudai92@gmail.com
140
+ executables:
141
+ - canopus
142
+ extensions: []
143
+ extra_rdoc_files: []
144
+ files:
145
+ - CHANGELOG.md
146
+ - LICENSE.txt
147
+ - README.md
148
+ - docs/adr/001-runtime-gem-components.md
149
+ - docs/adr/002-bounded-diff-engine.md
150
+ - docs/adr/003-background-language-analysis.md
151
+ - docs/adr/004-persistent-display-map.md
152
+ - docs/adr/005-windows-runtime.md
153
+ - docs/adr/README.md
154
+ - docs/distribution.md
155
+ - docs/lsp.md
156
+ - docs/performance.md
157
+ - docs/snippets.md
158
+ - docs/vim.md
159
+ - docs/workspace_edits.md
160
+ - examples/native_smoke.rb
161
+ - examples/plugins/word_count.rb
162
+ - exe/canopus
163
+ - lib/canopus.rb
164
+ - lib/canopus/block_map.rb
165
+ - lib/canopus/buffer.rb
166
+ - lib/canopus/cli.rb
167
+ - lib/canopus/controller.rb
168
+ - lib/canopus/data_compat.rb
169
+ - lib/canopus/display_map.rb
170
+ - lib/canopus/display_map/line_builder.rb
171
+ - lib/canopus/display_map/line_set.rb
172
+ - lib/canopus/display_map/pending_line_set.rb
173
+ - lib/canopus/display_map/summary.rb
174
+ - lib/canopus/display_map/worker.rb
175
+ - lib/canopus/display_point.rb
176
+ - lib/canopus/editor.rb
177
+ - lib/canopus/editor/snippet_expandable.rb
178
+ - lib/canopus/error.rb
179
+ - lib/canopus/fold_map.rb
180
+ - lib/canopus/git.rb
181
+ - lib/canopus/git/blame.rb
182
+ - lib/canopus/git/commit.rb
183
+ - lib/canopus/git/corrupt_object.rb
184
+ - lib/canopus/git/diff.rb
185
+ - lib/canopus/git/index.rb
186
+ - lib/canopus/git/object_database.rb
187
+ - lib/canopus/git/pack.rb
188
+ - lib/canopus/git/repository.rb
189
+ - lib/canopus/git/status.rb
190
+ - lib/canopus/git/tree_entry.rb
191
+ - lib/canopus/icon_theme.rb
192
+ - lib/canopus/language.rb
193
+ - lib/canopus/language/background_analysis.rb
194
+ - lib/canopus/language/background_analysis/job.rb
195
+ - lib/canopus/language/background_analysis/scheduler.rb
196
+ - lib/canopus/language/definition.rb
197
+ - lib/canopus/language/document.rb
198
+ - lib/canopus/language/symbol.rb
199
+ - lib/canopus/language/syntax_worker.rb
200
+ - lib/canopus/lazy_rope.rb
201
+ - lib/canopus/lsp.rb
202
+ - lib/canopus/lsp/client.rb
203
+ - lib/canopus/lsp/error.rb
204
+ - lib/canopus/lsp/future.rb
205
+ - lib/canopus/lsp/future/subscription.rb
206
+ - lib/canopus/lsp/protocol.rb
207
+ - lib/canopus/lsp/server_error.rb
208
+ - lib/canopus/lsp/timeout.rb
209
+ - lib/canopus/lsp/transport.rb
210
+ - lib/canopus/markdown.rb
211
+ - lib/canopus/match_data_compat.rb
212
+ - lib/canopus/multi_buffer.rb
213
+ - lib/canopus/pane.rb
214
+ - lib/canopus/patch.rb
215
+ - lib/canopus/patch/composite.rb
216
+ - lib/canopus/patch/reload.rb
217
+ - lib/canopus/performance_recorder.rb
218
+ - lib/canopus/plugins.rb
219
+ - lib/canopus/plugins/api.rb
220
+ - lib/canopus/plugins/isolated_runtime.rb
221
+ - lib/canopus/plugins/local_runtime.rb
222
+ - lib/canopus/plugins/permission_denied.rb
223
+ - lib/canopus/plugins/registry.rb
224
+ - lib/canopus/project.rb
225
+ - lib/canopus/project/ignore_matcher.rb
226
+ - lib/canopus/project/search.rb
227
+ - lib/canopus/project/search_worker.rb
228
+ - lib/canopus/project/search_worker/cancelled.rb
229
+ - lib/canopus/project/search_worker/runner.rb
230
+ - lib/canopus/project/tree.rb
231
+ - lib/canopus/project/watcher.rb
232
+ - lib/canopus/regexp_compat.rb
233
+ - lib/canopus/save_conflict.rb
234
+ - lib/canopus/selection.rb
235
+ - lib/canopus/settings.rb
236
+ - lib/canopus/snippet.rb
237
+ - lib/canopus/snippet/transform.rb
238
+ - lib/canopus/tab_map.rb
239
+ - lib/canopus/terminal.rb
240
+ - lib/canopus/terminal/cell.rb
241
+ - lib/canopus/terminal/grid.rb
242
+ - lib/canopus/terminal/pty.rb
243
+ - lib/canopus/terminal/scrollback.rb
244
+ - lib/canopus/terminal/vt.rb
245
+ - lib/canopus/theme.rb
246
+ - lib/canopus/version.rb
247
+ - lib/canopus/vim.rb
248
+ - lib/canopus/vim/commandable.rb
249
+ - lib/canopus/vim/motionable.rb
250
+ - lib/canopus/vim/operator_capable.rb
251
+ - lib/canopus/vim/text_object_selectable.rb
252
+ - lib/canopus/workspace.rb
253
+ - lib/canopus/workspace/edit.rb
254
+ - lib/canopus/workspace/edit/executable.rb
255
+ - lib/canopus/workspace/edit/node.rb
256
+ - lib/canopus/workspace/edit/plan.rb
257
+ - lib/canopus/workspace/edit/resource_preparable.rb
258
+ - lib/canopus/workspace/file_change_aware.rb
259
+ - lib/canopus/workspace/file_previewable.rb
260
+ - lib/canopus/workspace/git_aware.rb
261
+ - lib/canopus/workspace/language_aware.rb
262
+ - lib/canopus/workspace/language_server_configurable.rb
263
+ - lib/canopus/workspace/project_searchable.rb
264
+ - lib/canopus/workspace/project_tree_editable.rb
265
+ - lib/canopus/workspace/session_persistable.rb
266
+ - lib/canopus/workspace/settings_aware.rb
267
+ - lib/canopus/workspace/view.rb
268
+ - lib/canopus/workspace/view/terminal_presentable.rb
269
+ - lib/canopus/wrap_map.rb
270
+ - sig/canopus.rbs
271
+ - sig/controller.rbs
272
+ - sig/display_stages.rbs
273
+ - sig/git.rbs
274
+ - sig/lsp.rbs
275
+ - sig/markdown.rbs
276
+ - sig/performance_recorder.rbs
277
+ - sig/search_services.rbs
278
+ - sig/services.rbs
279
+ - sig/snippet.rbs
280
+ - sig/terminal.rbs
281
+ - sig/vim.rbs
282
+ - sig/workspace_edits.rbs
283
+ - sig/workspace_services.rbs
284
+ - tools/certify_snippet_regex.rb
285
+ - tools/check_dependencies.rb
286
+ - tools/native_check.rb
287
+ - tools/package.rb
288
+ - tools/package_test.rb
289
+ homepage: https://github.com/noxdea/canopus
290
+ licenses:
291
+ - MIT
292
+ metadata:
293
+ source_code_uri: https://github.com/noxdea/canopus
294
+ changelog_uri: https://github.com/noxdea/canopus/blob/main/CHANGELOG.md
295
+ allowed_push_host: https://rubygems.org
296
+ rubygems_mfa_required: 'true'
297
+ rdoc_options: []
298
+ require_paths:
299
+ - lib
300
+ required_ruby_version: !ruby/object:Gem::Requirement
301
+ requirements:
302
+ - - ">="
303
+ - !ruby/object:Gem::Version
304
+ version: '3.1'
305
+ required_rubygems_version: !ruby/object:Gem::Requirement
306
+ requirements:
307
+ - - ">="
308
+ - !ruby/object:Gem::Version
309
+ version: '0'
310
+ requirements: []
311
+ rubygems_version: 4.0.19
312
+ specification_version: 4
313
+ summary: A pure Ruby text editor with persistent buffers and language tools
314
+ test_files: []