Skip to content

Commit 557ad5b

Browse files
three to go
1 parent 12a079e commit 557ad5b

File tree

6 files changed

+254
-107
lines changed

6 files changed

+254
-107
lines changed

crates/pg_lsp/src/b_server.rs

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,22 @@ use tower_lsp::{Client, LanguageServer};
77

88
use crate::client::client_flags::ClientFlags;
99
use crate::server::options::ClientConfigurationOptions;
10+
use crate::session::Session;
1011
use crate::utils::file_path;
1112
use crate::utils::normalize_uri;
1213
use crate::utils::to_proto;
13-
use crate::workspace_handler::WorkspaceHandler;
1414

1515
struct Server {
1616
client: Client,
17-
workspace_handler: WorkspaceHandler,
17+
session: Session,
1818
client_capabilities: RwLock<Option<ClientFlags>>,
1919
}
2020

2121
impl Server {
2222
pub async fn new(client: Client) -> Self {
2323
Self {
2424
client,
25-
workspace_handler: WorkspaceHandler::new(),
25+
session: Session::new(),
2626
client_capabilities: RwLock::new(None),
2727
}
2828
}
@@ -91,10 +91,8 @@ impl Server {
9191
async fn publish_diagnostics(&self, mut uri: Url) {
9292
normalize_uri(&mut uri);
9393

94-
let diagnostics = self
95-
.workspace_handler
96-
.get_diagnostics(file_path(&uri))
97-
.await;
94+
let url = file_path(&uri);
95+
let diagnostics = self.session.get_diagnostics(url).await;
9896

9997
let diagnostics: Vec<Diagnostic> = diagnostics
10098
.into_iter()
@@ -177,22 +175,79 @@ impl LanguageServer for Server {
177175
.is_some_and(|o| o.db_connection_string.is_some())
178176
{
179177
let conn_str = opts.unwrap().db_connection_string.unwrap();
180-
self.workspace_handler.change_db(conn_str).await;
178+
self.session.change_db(conn_str).await;
181179
return;
182180
}
183181
}
184182

183+
// if we couldn't pull settings from the client,
184+
// we'll try parsing the passed in params.
185185
let opts = self.parse_options_from_client(params.settings);
186186

187187
if opts
188188
.as_ref()
189189
.is_some_and(|o| o.db_connection_string.is_some())
190190
{
191191
let conn_str = opts.unwrap().db_connection_string.unwrap();
192-
self.workspace_handler.change_db(conn_str).await;
192+
self.session.change_db(conn_str).await;
193+
}
194+
}
195+
196+
async fn did_open(&self, params: DidOpenTextDocumentParams) {
197+
let mut uri = params.text_document.uri;
198+
normalize_uri(&mut uri);
199+
200+
let changed_urls = self
201+
.session
202+
.apply_doc_changes(
203+
file_path(url),
204+
params.text_document.version,
205+
params.text_document.text,
206+
)
207+
.await;
208+
209+
for url in changed_urls {
210+
self.publish_diagnostics(url).await;
211+
}
212+
}
213+
214+
async fn did_save(&self, params: DidSaveTextDocumentParams) {
215+
let mut uri = params.text_document.uri;
216+
normalize_uri(&mut uri);
217+
218+
self.publish_diagnostics(uri).await;
219+
220+
// TODO: "Compute Now"
221+
let changed_urls = self.session.recompute_and_get_changed_files();
222+
for url in changed_urls {
223+
self.publish_diagnostics(url).await;
193224
}
194225
}
195226

227+
async fn did_change(&self, params: DidChangeTextDocumentParams) {
228+
todo!()
229+
}
230+
231+
async fn did_close(&self, params: DidSaveTextDocumentParams) {
232+
let mut uri = params.text_document.uri;
233+
normalize_uri(&mut uri);
234+
let path = file_path(&uri);
235+
236+
self.session.on_file_closed(path);
237+
}
238+
239+
async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
240+
let mut uri = params.text_document.uri;
241+
normalize_uri(&mut uri);
242+
243+
let path = file_path(&uri);
244+
let range = params.range;
245+
246+
let actions = self.session.get_available_code_actions(path, range);
247+
248+
Ok(actions)
249+
}
250+
196251
async fn execute_command(
197252
&self,
198253
params: ExecuteCommandParams,
@@ -207,7 +262,7 @@ impl LanguageServer for Server {
207262
let stmt = serde_json::from_value(params)
208263
.map_err(|_| jsonrpc::Error::invalid_request())?;
209264

210-
match self.workspace_handler.run_stmt(stmt).await {
265+
match self.session.run_stmt(stmt).await {
211266
Ok(rows_affected) => {
212267
self.client
213268
.send_notification::<ShowMessage>(ShowMessageParams {

crates/pg_lsp/src/db_connection.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use tokio::task::JoinHandle;
88

99
#[derive(Debug)]
1010
pub(crate) struct DbConnection {
11-
pub pool: PgPool,
11+
pool: PgPool,
1212
connection_string: String,
1313
schema_update_handle: Option<JoinHandle<()>>,
1414
}
@@ -75,4 +75,8 @@ impl DbConnection {
7575

7676
Ok(())
7777
}
78+
79+
pub(crate) fn get_pool(&self) -> PgPool {
80+
self.pool.clone()
81+
}
7882
}

crates/pg_lsp/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ pub mod server;
44
mod utils;
55

66
mod b_server;
7-
mod workspace_handler;
7+
mod session;

crates/pg_lsp/src/server.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,9 @@ impl Server {
197197
})
198198
.unwrap();
199199
}
200+
200201
let changed = cloned_ide.compute(conn);
202+
201203
let urls = HashSet::<&str>::from_iter(
202204
changed.iter().map(|f| f.document_url.to_str().unwrap()),
203205
);
@@ -375,7 +377,7 @@ impl Server {
375377
DocumentChange::new(params.text_document.version, changes),
376378
);
377379

378-
let conn = self.db_conn.as_ref().map(|p| p.pool.clone());
380+
let conn = self.db_conn.as_ref().map(|p| p.get_pool());
379381
self.compute_debouncer.put(conn);
380382

381383
Ok(())

crates/pg_lsp/src/session.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
use std::{collections::HashSet, sync::Arc};
2+
3+
use pg_base_db::{Change, DocumentChange, PgLspPath};
4+
use pg_diagnostics::Diagnostic;
5+
use pg_workspace::Workspace;
6+
use tokio::sync::RwLock;
7+
use tower_lsp::lsp_types::{CodeAction, Range};
8+
9+
use crate::{db_connection::DbConnection, utils::line_index_ext::LineIndexExt};
10+
11+
pub struct Session {
12+
db: RwLock<Option<DbConnection>>,
13+
ide: Arc<RwLock<Workspace>>,
14+
}
15+
16+
impl Session {
17+
pub fn new() -> Self {
18+
let ide = Arc::new(RwLock::new(Workspace::new()));
19+
Self {
20+
db: RwLock::new(None),
21+
ide,
22+
}
23+
}
24+
25+
/// `update_db_connection` will update `Self`'s database connection.
26+
/// If the passed-in connection string is the same that we're already connected to, it's a noop.
27+
/// Otherwise, it'll first open a new connection, replace `Self`'s connection, and then close
28+
/// the old one.
29+
pub async fn change_db(&self, connection_string: String) -> anyhow::Result<()> {
30+
if self
31+
.db
32+
.read()
33+
.await
34+
.as_ref()
35+
// if the connection is already connected to the same database, do nothing
36+
.is_some_and(|c| c.connected_to(&connection_string))
37+
{
38+
return Ok(());
39+
}
40+
41+
let mut db = DbConnection::new(connection_string).await?;
42+
43+
let ide = self.ide.clone();
44+
db.listen_for_schema_updates(move |schema| {
45+
let _guard = ide.blocking_write().set_schema_cache(schema);
46+
});
47+
48+
let mut current_db = self.db.blocking_write();
49+
let old_db = current_db.replace(db);
50+
51+
if old_db.is_some() {
52+
let old_db = old_db.unwrap();
53+
old_db.close().await;
54+
}
55+
56+
Ok(())
57+
}
58+
59+
pub async fn run_stmt(&self, stmt: String) -> anyhow::Result<u64> {
60+
let db = self.db.read().await;
61+
db.as_ref()
62+
.expect("No Db Connection")
63+
.run_stmt(stmt)
64+
.await
65+
.map(|pg_query_result| pg_query_result.rows_affected())
66+
}
67+
68+
pub async fn on_file_closed(&self, path: PgLspPath) {
69+
let ide = self.ide.read().await;
70+
ide.remove_document(path);
71+
}
72+
73+
pub async fn get_diagnostics(&self, path: PgLspPath) -> Vec<(Diagnostic, Range)> {
74+
let ide = self.ide.read().await;
75+
76+
// make sure there are documents at the provided path before
77+
// trying to collect diagnostics.
78+
let doc = ide.documents.get(&path);
79+
if doc.is_none() {
80+
return vec![];
81+
}
82+
83+
ide.diagnostics(&path)
84+
.into_iter()
85+
.map(|d| {
86+
let range = doc
87+
.as_ref()
88+
.unwrap()
89+
.line_index
90+
.line_col_lsp_range(d.range)
91+
.unwrap();
92+
(d, range)
93+
})
94+
.collect()
95+
}
96+
97+
pub async fn apply_doc_changes(
98+
&self,
99+
path: PgLspPath,
100+
version: i32,
101+
text: String,
102+
) -> HashSet<String> {
103+
{
104+
let ide = self.ide.read().await;
105+
106+
let doc = ide.documents.get(&path);
107+
if doc.is_none() {
108+
return HashSet::new();
109+
}
110+
111+
ide.apply_change(
112+
path,
113+
DocumentChange::new(version, vec![Change { range: None, text }]),
114+
);
115+
}
116+
117+
self.recompute_and_get_changed_files()
118+
}
119+
120+
pub async fn recompute_and_get_changed_files(&self) -> HashSet<String> {
121+
let ide = self.ide.read().await;
122+
123+
let db = self.db.read().await;
124+
let pool = db.as_ref().map(|d| d.get_pool());
125+
126+
let changed_files = ide.compute(pool);
127+
128+
changed_files
129+
.into_iter()
130+
.map(|f| f.document_url.to_string_lossy().to_string())
131+
.collect()
132+
}
133+
134+
pub async fn get_available_code_actions(
135+
&self,
136+
path: PgLspPath,
137+
range: Range,
138+
) -> Option<Vec<CodeAction>> {
139+
let ide = self.ide.read().await;
140+
let db = self.db.read().await;
141+
142+
let doc = ide.documents.get(&path);
143+
if doc.is_none() || db.is_none() {
144+
return None;
145+
}
146+
147+
let doc = doc.unwrap();
148+
149+
let range = doc.line_index.offset_lsp_range(range).unwrap();
150+
151+
// for now, we only provide `ExcecuteStatementCommand`s.
152+
let actions = doc
153+
.statements_at_range(&range)
154+
.into_iter()
155+
.map(|stmt| {
156+
let cmd = ExecuteStatementCommand::command_type();
157+
let title = format!(
158+
"Execute '{}'",
159+
ExecuteStatementCommand::trim_statement(stmt.text.clone(), 50)
160+
);
161+
CodeAction {
162+
title: title.clone(),
163+
kind: None,
164+
edit: None,
165+
command: Some(Command {
166+
title,
167+
command: format!("pglsp.{}", cmd.id()),
168+
arguments: Some(vec![serde_json::to_value(stmt.text.clone()).unwrap()]),
169+
}),
170+
diagnostics: None,
171+
is_preferred: None,
172+
disabled: None,
173+
data: None,
174+
}
175+
})
176+
.collect();
177+
178+
Some(actions)
179+
}
180+
}

0 commit comments

Comments
 (0)