Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@
now generate a decoder and an encoder, respectively, for `Nil` values.
([Hari Mohan](https://github.com/seafoamteal))

- The language server now cleans up compilation engines when all project
files are closed ([Nonso Chukwurah](https://github.com/TrippleCCC))

### Formatter

### Bug fixes
Expand Down
12 changes: 10 additions & 2 deletions language-server/src/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,13 @@ impl Request {
#[derive(Debug)]
pub enum Notification {
/// A Gleam file has been modified in memory, and the new text is provided.
SourceFileChangedInMemory { path: Utf8PathBuf, text: String },
SourceFileChangedInMemory {
path: Utf8PathBuf,
opened: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this represent, and why do we need to track it?

Could you add documentation please 🙏

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this into separate notification enums. Should be a bit more clear

text: String,
},
/// A Gleam file has been saved or closed in the editor.
SourceFileMatchesDisc { path: Utf8PathBuf },
SourceFileMatchesDisc { path: Utf8PathBuf, closed: bool },
/// gleam.toml has changed.
ConfigFileChanged { path: Utf8PathBuf },
/// It's time to compile all open projects.
Expand All @@ -107,6 +111,7 @@ impl Notification {
let notification = Notification::SourceFileChangedInMemory {
path: super::path(&params.text_document.uri),
text: params.text_document.text,
opened: true,
};
Some(Message::Notification(notification))
}
Expand All @@ -115,6 +120,7 @@ impl Notification {
let notification = Notification::SourceFileChangedInMemory {
path: super::path(&params.text_document.uri),
text: params.content_changes.into_iter().next_back()?.text,
opened: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is it possible to have files edited in memory but not open? Isn't that impossible?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SourceFileChangedInMemory was used for both open and change notifications; the flag was used to diff between the two. I went ahead an separated like you suguested.

};
Some(Message::Notification(notification))
}
Expand All @@ -123,13 +129,15 @@ impl Notification {
let params = cast_notification::<DidSaveTextDocument>(notification);
let notification = Notification::SourceFileMatchesDisc {
path: super::path(&params.text_document.uri),
closed: false,
};
Some(Message::Notification(notification))
}
"textDocument/didClose" => {
let params = cast_notification::<DidCloseTextDocument>(notification);
let notification = Notification::SourceFileMatchesDisc {
path: super::path(&params.text_document.uri),
closed: true,
};
Some(Message::Notification(notification))
}
Expand Down
43 changes: 34 additions & 9 deletions language-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub struct LanguageServer<'a, IO> {
outside_of_project_feedback: FeedbackBookKeeper,
router: Router<IO, ConnectionProgressReporter<'a>>,
changed_projects: HashSet<Utf8PathBuf>,
open_files: HashSet<Utf8PathBuf>,
io: FileSystemProxy<IO>,
}

Expand All @@ -63,6 +64,7 @@ where
connection: connection.into(),
initialise_params,
changed_projects: HashSet::new(),
open_files: HashSet::new(),
outside_of_project_feedback: FeedbackBookKeeper::default(),
router,
io,
Expand Down Expand Up @@ -131,12 +133,35 @@ where
.expect("channel send LSP response")
}

fn cleanup_engine(&mut self, path: &Utf8PathBuf) {
_ = self.open_files.remove(path);
if let Some(project_path) = self.router.project_path(path) {
if self
.open_files
.iter()
.any(|path| path.starts_with(&project_path))
{
self.router.delete_engine_for_path(&project_path);
}
}
}

fn handle_notification(&mut self, notification: Notification) {
let feedback = match notification {
Notification::CompilePlease => self.compile_please(),
Notification::SourceFileMatchesDisc { path } => self.discard_in_memory_cache(path),
Notification::SourceFileChangedInMemory { path, text } => {
self.cache_file_in_memory(path, text)
Notification::SourceFileMatchesDisc { path, closed } => {
let feedback = self.discard_in_memory_cache(&path);
if closed {
self.cleanup_engine(&path);
}
feedback
}
Notification::SourceFileChangedInMemory { path, opened, text } => {
let feedback = self.cache_file_in_memory(&path, text);
if opened {
_ = self.open_files.insert(path);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come we only record the file as opened if there's no diagnostics?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this check. We actually should still track that the file is open even if we don't return diagnostics.

feedback
}
Notification::ConfigFileChanged { path } => self.watched_files_changed(path),
};
Expand Down Expand Up @@ -428,17 +453,17 @@ where
self.respond_with_engine(path, |engine| engine.find_references(params))
}

fn cache_file_in_memory(&mut self, path: Utf8PathBuf, text: String) -> Feedback {
self.project_changed(&path);
if let Err(error) = self.io.write_mem_cache(&path, &text) {
fn cache_file_in_memory(&mut self, path: &Utf8PathBuf, text: String) -> Feedback {
self.project_changed(path);
if let Err(error) = self.io.write_mem_cache(path, &text) {
return self.outside_of_project_feedback.error(error);
}
Feedback::none()
}

fn discard_in_memory_cache(&mut self, path: Utf8PathBuf) -> Feedback {
self.project_changed(&path);
if let Err(error) = self.io.delete_mem_cache(&path) {
fn discard_in_memory_cache(&mut self, path: &Utf8PathBuf) -> Feedback {
self.project_changed(path);
if let Err(error) = self.io.delete_mem_cache(path) {
return self.outside_of_project_feedback.error(error);
}
Feedback::none()
Expand Down