1use crate::{Supermaven, SupermavenCompletionStateId};
2use anyhow::Result;
3use editor::{Direction, InlineCompletionProvider};
4use futures::StreamExt as _;
5use gpui::{AppContext, Model, ModelContext, Task};
6use language::{language_settings::all_language_settings, Anchor, Buffer};
7use std::time::Duration;
8
9pub const DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
10
11pub struct SupermavenCompletionProvider {
12 supermaven: Model<Supermaven>,
13 completion_id: Option<SupermavenCompletionStateId>,
14 pending_refresh: Task<Result<()>>,
15}
16
17impl SupermavenCompletionProvider {
18 pub fn new(supermaven: Model<Supermaven>) -> Self {
19 Self {
20 supermaven,
21 completion_id: None,
22 pending_refresh: Task::ready(Ok(())),
23 }
24 }
25}
26
27impl InlineCompletionProvider for SupermavenCompletionProvider {
28 fn is_enabled(&self, buffer: &Model<Buffer>, cursor_position: Anchor, cx: &AppContext) -> bool {
29 if !self.supermaven.read(cx).is_enabled() {
30 return false;
31 }
32
33 let buffer = buffer.read(cx);
34 let file = buffer.file();
35 let language = buffer.language_at(cursor_position);
36 let settings = all_language_settings(file, cx);
37 settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()))
38 }
39
40 fn refresh(
41 &mut self,
42 buffer_handle: Model<Buffer>,
43 cursor_position: Anchor,
44 debounce: bool,
45 cx: &mut ModelContext<Self>,
46 ) {
47 let Some(mut completion) = self.supermaven.update(cx, |supermaven, cx| {
48 supermaven.complete(&buffer_handle, cursor_position, cx)
49 }) else {
50 return;
51 };
52
53 self.pending_refresh = cx.spawn(|this, mut cx| async move {
54 if debounce {
55 cx.background_executor().timer(DEBOUNCE_TIMEOUT).await;
56 }
57
58 while let Some(()) = completion.updates.next().await {
59 this.update(&mut cx, |this, cx| {
60 this.completion_id = Some(completion.id);
61 cx.notify();
62 })?;
63 }
64 Ok(())
65 });
66 }
67
68 fn cycle(
69 &mut self,
70 _buffer: Model<Buffer>,
71 _cursor_position: Anchor,
72 _direction: Direction,
73 _cx: &mut ModelContext<Self>,
74 ) {
75 }
76
77 fn accept(&mut self, _cx: &mut ModelContext<Self>) {
78 self.pending_refresh = Task::ready(Ok(()));
79 self.completion_id = None;
80 }
81
82 fn discard(&mut self, _cx: &mut ModelContext<Self>) {
83 self.pending_refresh = Task::ready(Ok(()));
84 self.completion_id = None;
85 }
86
87 fn active_completion_text<'a>(
88 &'a self,
89 buffer: &Model<Buffer>,
90 cursor_position: Anchor,
91 cx: &'a AppContext,
92 ) -> Option<&'a str> {
93 let completion_text = self
94 .supermaven
95 .read(cx)
96 .completion(buffer, cursor_position, cx)?;
97
98 let completion_text = trim_to_end_of_line_unless_leading_newline(completion_text);
99
100 let completion_text = completion_text.trim_end();
101
102 if !completion_text.trim().is_empty() {
103 Some(completion_text)
104 } else {
105 None
106 }
107 }
108}
109
110fn trim_to_end_of_line_unless_leading_newline(text: &str) -> &str {
111 if has_leading_newline(&text) {
112 text
113 } else if let Some(i) = text.find('\n') {
114 &text[..i]
115 } else {
116 text
117 }
118}
119
120fn has_leading_newline(text: &str) -> bool {
121 for c in text.chars() {
122 if c == '\n' {
123 return true;
124 }
125 if !c.is_whitespace() {
126 return false;
127 }
128 }
129 false
130}