Skip to content

Commit

Permalink
project tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
elbywan committed Sep 15, 2024
1 parent 538ce5a commit 844ae62
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 27 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,19 +293,20 @@ use it as the entry point.
# Contents of a file at the root of the project.
# Will require the specs that call the library methods and enable the code analysis.
require "./spec/**"
```
```my_project_2

### Multiple projects

If you have multiple Crystal projects in a single folder (e.g. a monorepo), you can create a `.crystalline.yml` in the root, containing an array of paths to the individual Crystal projects:
If you have multiple Crystal projects in a single folder (e.g. a monorepo), you can add a `projects` field in the root `shard.yml` file, containing an array of paths or globs to the underlying Crystal projects:

```yml
projects:
- my_project_1
- my_project_2
crystalline:
projects:
- projects/my_project_1
- workspaces/**
```

Each of these projects should ideally contain the `shard.yml` containing the entry point as mentioned above. However, even if no shard.yml is present, `require`s will still be resolved relative to the project directory rather than the root directory.
Each of these projects must contain the `shard.yml`, ideally with the entry point as mentioned above. However, even if no entry point is present, `require`s will still be resolved relative to the project directory rather than the root directory.

## Features

Expand Down Expand Up @@ -383,7 +384,7 @@ LSP::Log.info { "log" }
```

Debug logs are deactivated by default, uncomment this line in
`src/crystalline/lsp/server.cr` to enable them:
`src/crystalline/main.cr` to enable them:

```crystal
# Uncomment:
Expand Down
5 changes: 4 additions & 1 deletion src/crystalline/analysis/analysis.cr
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ module Crystalline::Analysis
diagnostics = Diagnostics.new
reply_channel = Channel(Crystal::Compiler::Result | Exception).new

# LSP::Log.info { "sources: #{sources.map(&.filename)}" }
# LSP::Log.info { "lib_path: #{lib_path}" }

# Delegate heavy processing to a separate thread.
Thread.new do
wait_before_termination = Channel(Nil).new
Expand All @@ -37,7 +40,7 @@ module Crystalline::Analysis
if lib_path_override = lib_path
path = Crystal::CrystalPath.default_path_without_lib.split(Process::PATH_DELIMITER)
path.insert(0, lib_path_override)
compiler.crystal_path = Crystal::CrystalPath.new([path.join(Process::PATH_DELIMITER)])
compiler.crystal_path = Crystal::CrystalPath.new(path)
end

reply = begin
Expand Down
5 changes: 3 additions & 2 deletions src/crystalline/main.cr
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ module Crystalline
hover_provider: true,
definition_provider: true,
document_symbol_provider: true,
# signature_help_provider: LSP::SignatureHelpOptions.new(
# signature_help_provider: LSP::SignatureHelpOptions.new(
# trigger_characters: ["(", " "]
# ),
)
)

module EnvironmentConfig
# Add the `crystal env` environment variables to the current env.
Expand All @@ -47,6 +47,7 @@ module Crystalline

def self.init(*, input : IO = STDIN, output : IO = STDOUT)
EnvironmentConfig.run
# ::Log.setup(:debug, LSP::Log.backend.not_nil!)
server = LSP::Server.new(input, output, SERVER_CAPABILITIES)
Controller.new(server)
rescue ex
Expand Down
40 changes: 24 additions & 16 deletions src/crystalline/project.cr
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
require "yaml"

class Crystalline::Project
class ProjectFile
include YAML::Serializable

getter projects : Array(String) = [] of String
end

# The project root filesystem uri.
getter root_uri : URI
# The dependencies of the project, meaning the list of files required by the compilation target (entry point).
Expand Down Expand Up @@ -37,30 +31,44 @@ class Crystalline::Project

# Finds and returns an array of all projects in the workspace root.
def self.find_in_workspace_root(workspace_root_uri : URI) : Array(Project)
root_project = Project.new(workspace_root_uri)
# First, check for a Crystalline project file.
begin
crystalline_path = Path[workspace_root_uri.decoded_path, ".crystalline.yml"]
crystalline_file = File.open(crystalline_path) do |file|
ProjectFile.from_yaml(file)
path = Path[workspace_root_uri.decoded_path, "shard.yml"]
shards_yaml = File.open(path) do |file|
YAML.parse(file)
end

crystalline_file.projects.map do |p|
path = Path[workspace_root_uri.decoded_path, p].normalize
Project.new(URI.parse("file://#{path}"))
end
projects = shards_yaml.dig?("crystalline", "projects").try do |pjs|
Dir.glob(pjs.as_a.map(&.as_s)).reduce([] of Project) do |acc, match|
path = Path.new(match)

is_directory = File.directory?(path)
has_shard_yml = is_directory && File.exists?(Path[path, "shard.yml"])
is_not_lib = has_shard_yml && path.parent != "lib"

if is_directory && has_shard_yml && is_not_lib
normalized_path = Path[workspace_root_uri.decoded_path, path].normalize
acc << Project.new(URI.parse("file://#{normalized_path}"))
else
acc
end
end
end || [] of Project

projects << root_project
rescue e
# Failing that, create a project for the workspace root.
[Project.new(workspace_root_uri)]
[root_project]
end
end

# Finds the path-wise distance to the given file URI. If the file URI is not a
# dependency of this workspace's entry point, returns nil.
def distance_to_dependency(file_uri : URI) : Int32?
file_path = file_uri.decoded_path
return nil if !file_path.in?(dependencies)

relative = Path[file_uri.decoded_path].relative_to?(root_uri.decoded_path)

# If we can't get a relative path, give it the maximum distance possible, so
# it's the lowest priority.
return Int32::MAX if relative.nil?
Expand Down
13 changes: 12 additions & 1 deletion src/crystalline/workspace.cr
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ class Crystalline::Workspace
def initialize(server : LSP::Server, root_uri : String?)
if @root_uri = root_uri.try &->URI.parse(String)
@projects = Project.find_in_workspace_root @root_uri.not_nil!
if @projects.size > 0
LSP::Log.info {
<<-LOG
"[workspace] Found projects:
#{@projects.map(&.root_uri.decoded_path).join('\n')}
LOG
}
end
end
end

Expand Down Expand Up @@ -116,6 +124,9 @@ class Crystalline::Workspace
end

project = Project.best_fit_for_file(@projects, file_uri)

# LSP::Log.info { "Compiling #{file_uri}, project: #{project.try(&.root_uri.decoded_path)}" }

if project && (entry_point = project.entry_point?)
target = entry_point
progress = Progress.new(
Expand Down Expand Up @@ -198,7 +209,7 @@ class Crystalline::Workspace
select
when result = sync_channel.receive
result
# Just in case…
# Just in case…
when timeout 120.seconds
progress.send_progress_end(server)
nil
Expand Down

0 comments on commit 844ae62

Please sign in to comment.