Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
- Add edits should be applied synced to the external storage (Gist) in the same order they were applied to the file
- No excessive synchronizations: as few requests to the external storage as possible

## Architecture v0.0.7

### Extension

- LSP server's path watcher store notifies LSP client with an error message if a file sync has failed or there is an internal file watcher error. These messages are displayed by Zed as popups.

### CLI tool

- We don't try to load credentials from the Zed settings file anymore because it's creates an inconvenient user flow. The only option that CLI tool provides is the interactive input of credentials.

## Architecture v0.0.6

### CLI tool
Expand Down
19 changes: 8 additions & 11 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,36 +10,33 @@
- [x] Forbid the usage of unwrap and expect for Option, Result
- [x] Use serde_json from zed_extension_api, not directly
- [x] To support multiple sync providers, implement a Client trait and use it for all sync operations. Move it to the "common" crate.
- [~] **Add unit and integration tests**
- [ ] Test all error-returning code paths and ensure that all error conditions are either properly logged and/or reported back to Zed in form or a JSON-RPC error response
- [ ] Revamp README before publishing the extension to make it easier to consume and start using the extension immediately
- [ ] Add secrecy crate and use it for all usages of the Github token
- [~] **Add unit tests for all important business logic types**
- [ ] Prepare README for the initial public release (see Documentation)

## LSP server

- [x] Report a sync error in a visible way (crash the server? know how to report an LSP error inside Zed?)
- [x] To support multiple sync providers, implement a SyncClient trait and use it for all sync operations. Move it to the "common" crate.
- [ ] Manually save a settings file on its open (before adding to the watch list) to handle the case when the LSP server is restarted after the initialization_options are changes in settings.json file.
- [ ] Test all error-returning code paths and ensure that all error conditions are either properly logged and/or reported back to Zed in form or a JSON-RPC error response
- [ ] Ensure that restarting or shutting down the LSP server doesn't prevent the last coming updates from getting synced; otherwise, mitigate that
- [ ] Report a sync error in a visible way (crash the server? know how to report an LSP error inside Zed?)
- [ ] Backup installed themes to the gist automatically
- [ ] Create a true integration test when a server is spawned, it's fed with JSON-RPC messages, and Github API URL is mocked via env var to point to a local mock server as another process
- [ ] After implementing naive changes persistence (sync files as FS events come), seek the ways to improve it (e.g. queuing events)
– [ ] (experimental) Rewrite the LSP server to use structured async concurrency, thread-per-code async runtime, and get rid of Arc's around every data structure
- [ ] Add secrecy crate and use it for all usages of the Github token
- [ ] To support multiple sync providers, implement a SyncClient trait and use it for all sync operations. Move it to the "common" crate.
- [ ] Report a sync error in a visible way (crash the server? know how to report an LSP error inside Zed?)
- [ ] Backup installed themes to the gist automatically
- [ ] Use type-state pattern to guarantee that one cannot watch/unwatch a path using non-started WatcherSet or PathStore
- [ ] Add secrecy crate and use it for all usages of the Github token

### CLI tool

- [ ] Add an option to create a new gist on the fly, copy settings to it and start using it from now on
- [ ] Handle errors more beautifully, introduce the dedicated Error type if needed
- [ ] Log output through tracing subscriber and/or add coloring of various levels of output messages
– [ ] Add cross-platform colored plain chars for CLI output instead of colored circle emojis
- [ ] Refactor to get rid of the InteractiveIO trait in favor of BufRead + Write type (trait), see <https://t.me/rustlang_ua/69909/132141>
- [ ] Add an option to create a new gist on the fly, copy settings to it and start using it from now on

## CI

- [ ] Enable ["cancel-in-progress"](https://share.google/Vk4zJKCbkerc5BAfC) for CI builds
- [x] Add matrix to compile for Windows on ARM64
- [x] Speed up the build if possible (caching, Docker images, etc.)
- [ ] ~~Extract "compile" as a separate local Github action~~ No need for that, because "compile" is used only for release.
Expand Down
2 changes: 1 addition & 1 deletion common/src/sync/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub trait Client: Send + Sync {
}

#[derive(Error, Debug)]
#[error("Error processing file {file_name}: {error}")]
#[error("Error syncing file {file_name}: {error}")]
pub struct FileError {
file_name: String,
error: Error,
Expand Down
1 change: 1 addition & 0 deletions common/src/sync/local_file_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::path::PathBuf;

use anyhow::{Result, anyhow};

#[derive(Debug, PartialEq, Eq)]
pub struct LocalFileData {
pub path: PathBuf,
pub filename: String,
Expand Down
8 changes: 6 additions & 2 deletions lsp/src/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ use std::sync::Arc;

use anyhow::Result;
use common::sync::GithubClient;
#[cfg(not(test))]
use tower_lsp::Client as LspClient;

#[cfg(test)]
use crate::mocks::MockLspClient as LspClient;
use crate::watching::PathStore;

#[derive(Debug)]
Expand All @@ -11,9 +15,9 @@ pub struct AppState {
}

impl AppState {
pub fn new(gist_id: String, github_token: String) -> Result<Self> {
pub fn new(gist_id: String, github_token: String, lsp_client: Arc<LspClient>) -> Result<Self> {
let sync_client = Arc::new(GithubClient::new(gist_id, github_token)?);
let watched_paths = PathStore::new(sync_client)?;
let watched_paths = PathStore::new(sync_client, lsp_client)?;

Ok(Self { watched_paths })
}
Expand Down
26 changes: 18 additions & 8 deletions lsp/src/backend.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::path::PathBuf;
use std::sync::{Mutex, OnceLock};
use std::sync::{Arc, Mutex, OnceLock};

use anyhow::Result;
use common::config::Config;
#[cfg(not(test))]
use tower_lsp::Client as LspClient;
use tower_lsp::jsonrpc::Result as LspResult;
use tower_lsp::lsp_types::{DidCloseTextDocumentParams, DidOpenTextDocumentParams};
use tower_lsp::{
Client, LanguageServer,
LanguageServer,
lsp_types::{
InitializeParams, InitializeResult, InitializedParams, ServerCapabilities, ServerInfo,
TextDocumentSyncCapability, TextDocumentSyncOptions, WorkspaceServerCapabilities,
Expand All @@ -16,6 +18,8 @@ use tracing::{debug, error, info, instrument};
use zed_extension_api::serde_json::from_value;

use crate::app_state::AppState;
#[cfg(test)]
use crate::mocks::MockLspClient as LspClient;
use crate::watching::{ZedConfigFilePath, ZedConfigPathError};

const CARGO_PKG_NAME: &str = env!("CARGO_PKG_NAME");
Expand All @@ -27,12 +31,14 @@ pub struct Backend {
// Mutex is needed for interior mutability over a shared reference
// because LanguageServer trait methods accept &self (not &mut self).
app_state: OnceLock<Mutex<AppState>>,
lsp_client: LspClient,
}

impl Backend {
pub fn new(_client: Client) -> Self {
pub fn new(lsp_client: LspClient) -> Self {
Self {
app_state: OnceLock::new(),
lsp_client,
}
}

Expand Down Expand Up @@ -85,11 +91,15 @@ impl LanguageServer for Backend {
tower_lsp::jsonrpc::Error::internal_error()
})?;

let app_state = AppState::new(config.gist_id().into(), config.github_token().into())
.map_err(|err| {
error!("Failed to build the app state: {}", err);
tower_lsp::jsonrpc::Error::internal_error()
})?;
let app_state = AppState::new(
config.gist_id().into(),
config.github_token().into(),
Arc::new(self.lsp_client.clone()),
)
.map_err(|err| {
error!("Failed to build the app state: {}", err);
tower_lsp::jsonrpc::Error::internal_error()
})?;

#[allow(clippy::expect_used)]
self.app_state
Expand Down
14 changes: 10 additions & 4 deletions lsp/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#[cfg(not(test))]
use tower_lsp::{LspService, Server};
use tracing::info;

#[cfg(not(test))]
use crate::backend::Backend;

mod app_state;
mod backend;
mod logger;
#[cfg(test)]
mod mocks;
mod watching;

#[cfg(test)]
Expand All @@ -20,13 +24,15 @@ async fn main() {
env!("CARGO_PKG_VERSION")
);

let stdin = tokio::io::stdin();
let stdout = tokio::io::stdout();

#[cfg(not(test))] // to avoid "type mismatch in function arguments" error in LspService::new
let (service, socket) = LspService::new(Backend::new);

info!("LSP service created, starting server");
Server::new(stdin, stdout, socket).serve(service).await;

#[cfg(not(test))]
Server::new(tokio::io::stdin(), tokio::io::stdout(), socket)
.serve(service)
.await;

info!("Zed Settings Sync LSP server stopped");
}
22 changes: 22 additions & 0 deletions lsp/src/mocks.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use std::fmt;

use mockall::mock;
use tower_lsp::lsp_types::MessageType;

mock! {
pub LspClient {
pub fn show_message(&self, msg_type: MessageType, message: String) -> impl Future<Output = ()> + Send + Sync;
}

impl fmt::Debug for LspClient {
fn fmt<'a>(&self, f: &mut std::fmt::Formatter<'a>) -> std::fmt::Result {
f.debug_struct("LspClient").finish()
}
}

impl Clone for LspClient {
fn clone(&self) -> Self {
Self::default()
}
}
}
Loading