File indexing completed on 2024-04-21 05:49:03

0001 /*
0002     SPDX-FileCopyrightText: 2021 Ilia Kats <ilia-kats@gmx.net>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "completionmodel.h"
0008 #include "completiontable.h"
0009 
0010 #include <algorithm>
0011 #include <string>
0012 
0013 #include <QIcon>
0014 
0015 #include <KTextEditor/Document>
0016 #include <KTextEditor/View>
0017 
0018 bool startsWith(const Completion &comp, const std::u16string &prefix)
0019 {
0020     if (prefix.size() <= comp.completion_strlen)
0021         return std::char_traits<char16_t>::compare(prefix.data(), comp.completion, prefix.size()) == 0;
0022     return false;
0023 }
0024 
0025 LatexCompletionModel::LatexCompletionModel(QObject *parent)
0026     : KTextEditor::CodeCompletionModel(parent)
0027 {
0028 }
0029 
0030 void LatexCompletionModel::completionInvoked(KTextEditor::View *view,
0031                                              const KTextEditor::Range &range,
0032                                              KTextEditor::CodeCompletionModel::InvocationType invocationType)
0033 {
0034     Q_UNUSED(invocationType);
0035     beginResetModel();
0036     m_matches.first = m_matches.second = -1;
0037     auto word = view->document()->text(range).toStdU16String();
0038     const Completion *beginit = (Completion *)&completiontable;
0039     const Completion *endit = beginit + n_completions;
0040     if (!word.empty() && word[0] == QLatin1Char('\\')) {
0041         auto prefixrangestart = std::lower_bound(beginit, endit, word, [](const Completion &a, const std::u16string &b) -> bool {
0042             return startsWith(a, b) ? false : a.completion < b;
0043         });
0044         auto prefixrangeend = std::upper_bound(beginit, endit, word, [](const std::u16string &a, const Completion &b) -> bool {
0045             return startsWith(b, a) ? false : a < b.completion;
0046         });
0047         if (prefixrangestart != endit) {
0048             m_matches.first = prefixrangestart - beginit;
0049             m_matches.second = prefixrangeend - beginit;
0050         }
0051     }
0052     setRowCount(m_matches.second - m_matches.first);
0053     endResetModel();
0054 }
0055 
0056 bool LatexCompletionModel::shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position)
0057 {
0058     Q_UNUSED(view);
0059     Q_UNUSED(position);
0060     return userInsertion && latexexpr.match(insertedText).hasMatch();
0061 }
0062 
0063 bool LatexCompletionModel::shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString &currentCompletion)
0064 {
0065     if (view->cursorPosition() < range.start() || view->cursorPosition() > range.end())
0066         return true;
0067     return !latexexpr.match(currentCompletion).hasMatch();
0068 }
0069 
0070 KTextEditor::Range LatexCompletionModel::completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position)
0071 {
0072     auto text = view->document()->line(position.line());
0073     KTextEditor::Cursor start = position;
0074     int pos = text.left(position.column()).lastIndexOf(latexexpr);
0075     if (pos >= 0)
0076         start.setColumn(pos);
0077     return KTextEditor::Range(start, position);
0078 }
0079 
0080 void LatexCompletionModel::executeCompletionItem(KTextEditor::View *view, const KTextEditor::Range &word, const QModelIndex &index) const
0081 {
0082     view->document()->replaceText(word, data(index.sibling(index.row(), Postfix), Qt::DisplayRole).toString());
0083 }
0084 
0085 QVariant LatexCompletionModel::data(const QModelIndex &index, int role) const
0086 {
0087     if (role == UnimportantItemRole)
0088         return false;
0089     else if (role == InheritanceDepth)
0090         return 1;
0091 
0092     if (index.isValid() && index.row() < m_matches.second - m_matches.first) {
0093         const Completion &completion = completiontable[m_matches.first + index.row()];
0094         if (role == IsExpandable)
0095             return true; // if it's not expandable, the description will often be cut off
0096                          // because apprarently the ItemSelected role is not taken into account
0097                          // when determining the completion widget width. So expanding is
0098                          // the only way to make sure that the complete description is available.
0099         else if (role == ItemSelected || role == ExpandingWidget)
0100             return QStringLiteral("<table><tr><td>%1</td><td>%2</td></tr></table>")
0101                 .arg(QString::fromUtf16(completion.codepoint), QString::fromUtf16(completion.name));
0102         else if (role == Qt::DisplayRole) {
0103             if (index.column() == Name)
0104                 return QString::fromUtf16(completion.completion);
0105             else if (index.column() == Postfix)
0106                 return QString::fromUtf16(completion.chars);
0107         } else if (index.column() == Icon && role == Qt::DecorationRole) {
0108             static const QIcon icon(QIcon::fromTheme(QStringLiteral("texcompiler")));
0109             return icon;
0110         }
0111     }
0112     return QVariant();
0113 }
0114 
0115 #include "moc_completionmodel.cpp"