#ifndef OLLIE_GHOST_H #define OLLIE_GHOST_H #include #include #include #include #include #include #include #include #include #include #include class GhostOverlay; /** * OllieGhostProvider renders AI code completions as translucent gray text * at the cursor position using KTextEditor's InlineNoteProvider API. * * The first line of a multi-line suggestion is rendered as an inline note * at end-of-line. Continuation lines are rendered via a floating overlay * widget positioned below the cursor line. * * Flow: * 1. Cursor moves or text changes → debounce timer starts (500ms) * 2. Timer fires → gather prefix/suffix, call 9P Complete asynchronously * 3. Result arrives → store suggestion lines, emit inlineNotesChanged * 4. paintInlineNote renders first line; GhostOverlay renders the rest * 5. Tab → accept (insert text), any other input → dismiss */ class OllieGhostProvider : public KTextEditor::InlineNoteProvider { Q_OBJECT public: explicit OllieGhostProvider(KTextEditor::View *view, class OllieKatePlugin *plugin, QObject *parent = nullptr); ~OllieGhostProvider() override; // InlineNoteProvider interface QList inlineNotes(int line) const override; QSize inlineNoteSize(const KTextEditor::InlineNote ¬e) const override; void paintInlineNote(const KTextEditor::InlineNote ¬e, QPainter &painter, Qt::LayoutDirection direction) const override; /// Accept the current suggestion (insert into document). Returns true if accepted. bool accept(); /// Dismiss the current suggestion without inserting. void dismiss(); /// Cycle to next (+1) or previous (-1) suggestion. void cycle(int direction); /// Returns true if a suggestion is currently visible. bool hasSuggestion() const; /// Returns the view this provider is attached to. KTextEditor::View *view() const { return m_view; } /// Enable or disable ghost completions. void setEnabled(bool enabled); bool isEnabled() const { return m_enabled; } public slots: void requestCompletion(); private slots: void onCursorPositionChanged(); void onTextChanged(); void onCompletionReply(const QString &result); private: void clearSuggestion(); void showSuggestionFromHistory(); void showOverlay(); void hideOverlay(); QString gatherPrefix() const; QString gatherSuffix() const; KTextEditor::View *m_view; OllieKatePlugin *m_plugin; QTimer *m_debounce; GhostOverlay *m_overlay = nullptr; // Current suggestion state QStringList m_suggestionLines; // suggestion split by newline int m_suggestionStartLine = -1; // document line where suggestion starts int m_suggestionStartCol = -1; // column (end of line where cursor was) KTextEditor::Cursor m_triggerCursor; // cursor position when completion was triggered // Suggestion history for cycling QList m_history; // all suggestions received at this cursor position int m_historyIndex = -1; // current index into m_history bool m_enabled = true; // ghost completions active bool m_requesting = false; // true while 9P call is in flight }; /** * GhostOverlay is a transparent widget that paints continuation lines * of a multi-line ghost suggestion over the editor viewport. */ class GhostOverlay : public QWidget { Q_OBJECT public: explicit GhostOverlay(QWidget *parent = nullptr); void setLines(const QStringList &lines, const QFont &font); void clear(); protected: void paintEvent(QPaintEvent *event) override; private: QStringList m_lines; QFont m_font; }; #endif // OLLIE_GHOST_H