1 /*
2     SPDX-FileCopyrightText: 2001-2010 Christoph Cullmann <cullmann@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "katecmd.h"
8 #include "kateglobal.h"
9 
10 #include "katepartdebug.h"
11 #include <KCompletionMatches>
12 
13 #include <ktexteditor/command.h>
14 
15 // BEGIN KateCmd
16 #define CMD_HIST_LENGTH 256
17 
KateCmd()18 KateCmd::KateCmd()
19 {
20     m_cmdCompletion.addItem(QStringLiteral("help"));
21 }
22 
23 KateCmd::~KateCmd() = default;
24 
registerCommand(KTextEditor::Command * cmd)25 bool KateCmd::registerCommand(KTextEditor::Command *cmd)
26 {
27     const QStringList &l = cmd->cmds();
28 
29     for (int z = 0; z < l.count(); z++) {
30         if (m_dict.contains(l[z])) {
31             qCDebug(LOG_KTE) << "Command already registered: " << l[z] << ". Aborting.";
32             return false;
33         }
34     }
35 
36     for (int z = 0; z < l.count(); z++) {
37         m_dict.insert(l[z], cmd);
38         // qCDebug(LOG_KTE)<<"Inserted command:"<<l[z];
39     }
40 
41     m_cmds += l;
42     m_cmdCompletion.insertItems(l);
43 
44     return true;
45 }
46 
unregisterCommand(KTextEditor::Command * cmd)47 bool KateCmd::unregisterCommand(KTextEditor::Command *cmd)
48 {
49     QStringList l;
50 
51     QHash<QString, KTextEditor::Command *>::const_iterator i = m_dict.constBegin();
52     while (i != m_dict.constEnd()) {
53         if (i.value() == cmd) {
54             l << i.key();
55         }
56         ++i;
57     }
58 
59     for (QStringList::Iterator it1 = l.begin(); it1 != l.end(); ++it1) {
60         m_dict.remove(*it1);
61         m_cmdCompletion.removeItem(*it1);
62         // qCDebug(LOG_KTE)<<"Removed command:"<<*it1;
63     }
64 
65     return true;
66 }
67 
queryCommand(const QString & cmd) const68 KTextEditor::Command *KateCmd::queryCommand(const QString &cmd) const
69 {
70     // a command can be named ".*[\w\-]+" with the constrain that it must
71     // contain at least one letter.
72     int f = 0;
73     bool b = false;
74 
75     // special case: '-' and '_' can be part of a command name, but if the
76     // command is 's' (substitute), it should be considered the delimiter and
77     // should not be counted as part of the command name
78     if (cmd.length() >= 2 && cmd.at(0) == QLatin1Char('s') && (cmd.at(1) == QLatin1Char('-') || cmd.at(1) == QLatin1Char('_'))) {
79         return m_dict.value(QStringLiteral("s"));
80     }
81 
82     for (; f < cmd.length(); f++) {
83         if (cmd[f].isLetter()) {
84             b = true;
85         }
86         if (b && (!cmd[f].isLetterOrNumber() && cmd[f] != QLatin1Char('-') && cmd[f] != QLatin1Char('_'))) {
87             break;
88         }
89     }
90     return m_dict.value(cmd.left(f));
91 }
92 
commands() const93 QList<KTextEditor::Command *> KateCmd::commands() const
94 {
95     return m_dict.values();
96 }
97 
commandList() const98 QStringList KateCmd::commandList() const
99 {
100     return m_cmds;
101 }
102 
self()103 KateCmd *KateCmd::self()
104 {
105     return KTextEditor::EditorPrivate::self()->cmdManager();
106 }
107 
appendHistory(const QString & cmd)108 void KateCmd::appendHistory(const QString &cmd)
109 {
110     if (!m_history.isEmpty()) { // this line should be backported to 3.x
111         if (m_history.last() == cmd) {
112             return;
113         }
114     }
115 
116     if (m_history.count() == CMD_HIST_LENGTH) {
117         m_history.removeFirst();
118     }
119 
120     m_history.append(cmd);
121 }
122 
fromHistory(int index) const123 const QString KateCmd::fromHistory(int index) const
124 {
125     if (index < 0 || index > m_history.count() - 1) {
126         return QString();
127     }
128     return m_history[index];
129 }
130 
commandCompletionObject()131 KCompletion *KateCmd::commandCompletionObject()
132 {
133     return &m_cmdCompletion;
134 }
135 // END KateCmd
136 
137 // BEGIN KateCmdShellCompletion
138 /*
139    A lot of the code in the below class is copied from
140    kdelibs/kio/kio/kshellcompletion.cpp
141    SPDX-FileCopyrightText: 2000 David Smith <dsmith@algonet.se>
142    SPDX-FileCopyrightText: 2004 Anders Lund <anders@alweb.dk>
143 */
KateCmdShellCompletion()144 KateCmdShellCompletion::KateCmdShellCompletion()
145     : KCompletion()
146 {
147     m_word_break_char = QLatin1Char(' ');
148     m_quote_char1 = QLatin1Char('\"');
149     m_quote_char2 = QLatin1Char('\'');
150     m_escape_char = QLatin1Char('\\');
151 }
152 
makeCompletion(const QString & text)153 QString KateCmdShellCompletion::makeCompletion(const QString &text)
154 {
155     // Split text at the last unquoted space
156     //
157     splitText(text, m_text_start, m_text_compl);
158 
159     // Make completion on the last part of text
160     //
161     return KCompletion::makeCompletion(m_text_compl);
162 }
163 
postProcessMatch(QString * match) const164 void KateCmdShellCompletion::postProcessMatch(QString *match) const
165 {
166     if (match->isNull()) {
167         return;
168     }
169 
170     match->prepend(m_text_start);
171 }
172 
postProcessMatches(QStringList * matches) const173 void KateCmdShellCompletion::postProcessMatches(QStringList *matches) const
174 {
175     for (QStringList::Iterator it = matches->begin(); it != matches->end(); it++) {
176         if (!(*it).isNull()) {
177             (*it).prepend(m_text_start);
178         }
179     }
180 }
181 
postProcessMatches(KCompletionMatches * matches) const182 void KateCmdShellCompletion::postProcessMatches(KCompletionMatches *matches) const
183 {
184     for (KCompletionMatches::Iterator it = matches->begin(); it != matches->end(); it++) {
185         if (!(*it).value().isNull()) {
186             (*it).value().prepend(m_text_start);
187         }
188     }
189 }
190 
splitText(const QString & text,QString & text_start,QString & text_compl) const191 void KateCmdShellCompletion::splitText(const QString &text, QString &text_start, QString &text_compl) const
192 {
193     bool in_quote = false;
194     bool escaped = false;
195     QChar p_last_quote_char;
196     int last_unquoted_space = -1;
197     int end_space_len = 0;
198 
199     for (int pos = 0; pos < text.length(); pos++) {
200         end_space_len = 0;
201 
202         if (escaped) {
203             escaped = false;
204         } else if (in_quote && text[pos] == p_last_quote_char) {
205             in_quote = false;
206         } else if (!in_quote && text[pos] == m_quote_char1) {
207             p_last_quote_char = m_quote_char1;
208             in_quote = true;
209         } else if (!in_quote && text[pos] == m_quote_char2) {
210             p_last_quote_char = m_quote_char2;
211             in_quote = true;
212         } else if (text[pos] == m_escape_char) {
213             escaped = true;
214         } else if (!in_quote && text[pos] == m_word_break_char) {
215             end_space_len = 1;
216 
217             while (pos + 1 < text.length() && text[pos + 1] == m_word_break_char) {
218                 end_space_len++;
219                 pos++;
220             }
221 
222             if (pos + 1 == text.length()) {
223                 break;
224             }
225 
226             last_unquoted_space = pos;
227         }
228     }
229 
230     text_start = text.left(last_unquoted_space + 1);
231 
232     // the last part without trailing blanks
233     text_compl = text.mid(last_unquoted_space + 1);
234 }
235 
236 // END KateCmdShellCompletion
237