example: Fix lint performance in CodeEditor example. (#1259)
Move AutoCorrect lint to background.
This commit is contained in:
parent
8a3ef51ea1
commit
3fe9bc8de0
1 changed files with 108 additions and 57 deletions
|
|
@ -1,4 +1,10 @@
|
||||||
use std::{cell::RefCell, ops::Range, rc::Rc, str::FromStr, sync::Arc, time::Duration};
|
use std::{
|
||||||
|
ops::Range,
|
||||||
|
rc::Rc,
|
||||||
|
str::FromStr,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Ok;
|
use anyhow::Ok;
|
||||||
use gpui::{prelude::FluentBuilder, *};
|
use gpui::{prelude::FluentBuilder, *};
|
||||||
|
|
@ -43,6 +49,7 @@ pub struct Example {
|
||||||
soft_wrap: bool,
|
soft_wrap: bool,
|
||||||
lsp_store: ExampleLspStore,
|
lsp_store: ExampleLspStore,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
|
_lint_task: Task<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
|
@ -111,7 +118,9 @@ const LANGUAGES: [(Lang, &'static str); 12] = [
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ExampleLspStore {
|
pub struct ExampleLspStore {
|
||||||
completions: Arc<Vec<CompletionItem>>,
|
completions: Arc<Vec<CompletionItem>>,
|
||||||
code_actions: Rc<RefCell<Vec<(Range<usize>, CodeAction)>>>,
|
code_actions: Arc<RwLock<Vec<(Range<usize>, CodeAction)>>>,
|
||||||
|
diagnostics: Arc<RwLock<Vec<Diagnostic>>>,
|
||||||
|
dirty: Arc<RwLock<bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExampleLspStore {
|
impl ExampleLspStore {
|
||||||
|
|
@ -123,9 +132,38 @@ impl ExampleLspStore {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
completions: Arc::new(completions),
|
completions: Arc::new(completions),
|
||||||
code_actions: Rc::new(RefCell::new(vec![])),
|
code_actions: Arc::new(RwLock::new(vec![])),
|
||||||
|
diagnostics: Arc::new(RwLock::new(vec![])),
|
||||||
|
dirty: Arc::new(RwLock::new(false)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn diagnostics(&self) -> Vec<Diagnostic> {
|
||||||
|
let guard = self.diagnostics.read().unwrap();
|
||||||
|
guard.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_diagnostics(&self, diagnostics: Vec<Diagnostic>) {
|
||||||
|
let mut guard = self.diagnostics.write().unwrap();
|
||||||
|
*guard = diagnostics;
|
||||||
|
*self.dirty.write().unwrap() = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn code_actions(&self) -> Vec<(Range<usize>, CodeAction)> {
|
||||||
|
let guard = self.code_actions.read().unwrap();
|
||||||
|
guard.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_code_actions(&self, code_actions: Vec<(Range<usize>, CodeAction)>) {
|
||||||
|
let mut guard = self.code_actions.write().unwrap();
|
||||||
|
*guard = code_actions;
|
||||||
|
*self.dirty.write().unwrap() = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
let guard = self.dirty.read().unwrap();
|
||||||
|
*guard
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CompletionProvider for ExampleLspStore {
|
impl CompletionProvider for ExampleLspStore {
|
||||||
|
|
@ -146,7 +184,7 @@ impl CompletionProvider for ExampleLspStore {
|
||||||
|
|
||||||
// Simulate to delay for fetching completions
|
// Simulate to delay for fetching completions
|
||||||
let items = self.completions.clone();
|
let items = self.completions.clone();
|
||||||
cx.background_executor().spawn(async move {
|
cx.background_spawn(async move {
|
||||||
// Simulate a slow completion source, to test Editor async handling.
|
// Simulate a slow completion source, to test Editor async handling.
|
||||||
smol::Timer::after(Duration::from_millis(20)).await;
|
smol::Timer::after(Duration::from_millis(20)).await;
|
||||||
|
|
||||||
|
|
@ -186,7 +224,7 @@ impl CodeActionProvider for ExampleLspStore {
|
||||||
_cx: &mut App,
|
_cx: &mut App,
|
||||||
) -> Task<Result<Vec<CodeAction>>> {
|
) -> Task<Result<Vec<CodeAction>>> {
|
||||||
let mut actions = vec![];
|
let mut actions = vec![];
|
||||||
for (node_range, code_action) in self.code_actions.borrow().iter() {
|
for (node_range, code_action) in self.code_actions().iter() {
|
||||||
if !(range.start >= node_range.start && range.end <= node_range.end) {
|
if !(range.start >= node_range.start && range.end <= node_range.end) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -498,6 +536,7 @@ impl Example {
|
||||||
soft_wrap: false,
|
soft_wrap: false,
|
||||||
lsp_store,
|
lsp_store,
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
|
_lint_task: Task::ready(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -572,65 +611,65 @@ impl Example {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lint_document(&self, cx: &mut Context<Self>) {
|
fn lint_document(&mut self, cx: &mut Context<Self>) {
|
||||||
// Subscribe to input changes and perform linting with AutoCorrect for markers example.
|
let language = self.language.name().to_string();
|
||||||
let value = self.editor.read(cx).value().clone();
|
let lsp_store = self.lsp_store.clone();
|
||||||
let result = autocorrect::lint_for(value.as_str(), self.language.name());
|
let text = self.editor.read(cx).text().clone();
|
||||||
|
|
||||||
let mut code_actions = vec![];
|
self._lint_task = cx.background_spawn(async move {
|
||||||
self.editor.update(cx, |state, cx| {
|
let value = text.to_string();
|
||||||
let text = state.text().clone();
|
let result = autocorrect::lint_for(value.as_str(), &language);
|
||||||
state.diagnostics_mut().map(|diagnostics| {
|
|
||||||
diagnostics.clear();
|
|
||||||
for item in result.lines.iter() {
|
|
||||||
let severity = match item.severity {
|
|
||||||
autocorrect::Severity::Error => DiagnosticSeverity::Warning,
|
|
||||||
autocorrect::Severity::Warning => DiagnosticSeverity::Hint,
|
|
||||||
autocorrect::Severity::Pass => DiagnosticSeverity::Info,
|
|
||||||
};
|
|
||||||
|
|
||||||
let line = item.line.saturating_sub(1); // Convert to 0-based index
|
let mut code_actions = vec![];
|
||||||
let col = item.col.saturating_sub(1); // Convert to 0-based index
|
let mut diagnostics = vec![];
|
||||||
|
|
||||||
let start = Position::new(line as u32, col as u32);
|
for item in result.lines.iter() {
|
||||||
let end = Position::new(line as u32, (col + item.old.chars().count()) as u32);
|
let severity = match item.severity {
|
||||||
let message = format!("AutoCorrect: {}", item.new);
|
autocorrect::Severity::Error => DiagnosticSeverity::Warning,
|
||||||
diagnostics.push(Diagnostic::new(start..end, message).with_severity(severity));
|
autocorrect::Severity::Warning => DiagnosticSeverity::Hint,
|
||||||
|
autocorrect::Severity::Pass => DiagnosticSeverity::Info,
|
||||||
|
};
|
||||||
|
|
||||||
let range = text.position_to_offset(&start)..text.position_to_offset(&end);
|
let line = item.line.saturating_sub(1); // Convert to 0-based index
|
||||||
|
let col = item.col.saturating_sub(1); // Convert to 0-based index
|
||||||
|
|
||||||
let text_edit = TextEdit {
|
let start = Position::new(line as u32, col as u32);
|
||||||
range: lsp_types::Range { start, end },
|
let end = Position::new(line as u32, (col + item.old.chars().count()) as u32);
|
||||||
new_text: item.new.clone(),
|
let message = format!("AutoCorrect: {}", item.new);
|
||||||
|
diagnostics.push(Diagnostic::new(start..end, message).with_severity(severity));
|
||||||
|
|
||||||
|
let range = text.position_to_offset(&start)..text.position_to_offset(&end);
|
||||||
|
|
||||||
|
let text_edit = TextEdit {
|
||||||
|
range: lsp_types::Range { start, end },
|
||||||
|
new_text: item.new.clone(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let edit = WorkspaceEdit {
|
||||||
|
changes: Some(
|
||||||
|
std::iter::once((
|
||||||
|
lsp_types::Uri::from_str("file://example").unwrap(),
|
||||||
|
vec![text_edit],
|
||||||
|
))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
code_actions.push((
|
||||||
|
range,
|
||||||
|
CodeAction {
|
||||||
|
title: format!("Change to '{}'", item.new),
|
||||||
|
kind: Some(CodeActionKind::QUICKFIX),
|
||||||
|
edit: Some(edit),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let edit = WorkspaceEdit {
|
lsp_store.update_code_actions(code_actions.clone());
|
||||||
changes: Some(
|
lsp_store.update_diagnostics(diagnostics.clone());
|
||||||
std::iter::once((
|
|
||||||
lsp_types::Uri::from_str("file://example").unwrap(),
|
|
||||||
vec![text_edit],
|
|
||||||
))
|
|
||||||
.collect(),
|
|
||||||
),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
code_actions.push((
|
|
||||||
range,
|
|
||||||
CodeAction {
|
|
||||||
title: format!("Change to '{}'", item.new),
|
|
||||||
kind: Some(CodeActionKind::QUICKFIX),
|
|
||||||
edit: Some(edit),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
self.lsp_store.code_actions.replace(code_actions.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cx.notify();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -639,6 +678,18 @@ impl Render for Example {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
self.update_highlighter(window, cx);
|
self.update_highlighter(window, cx);
|
||||||
|
|
||||||
|
// Update diagnostics
|
||||||
|
if self.lsp_store.is_dirty() {
|
||||||
|
let diagnostics = self.lsp_store.diagnostics();
|
||||||
|
self.editor.update(cx, |state, cx| {
|
||||||
|
state.diagnostics_mut().map(|set| {
|
||||||
|
set.clear();
|
||||||
|
set.extend(diagnostics);
|
||||||
|
});
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
v_flex().size_full().child(
|
v_flex().size_full().child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.id("source")
|
.id("source")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue