supermaven_completion_provider.rs

  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}