File indexing completed on 2024-09-15 03:39:03
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000 David Smith <dsmith@algonet.se> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kshellcompletion.h" 0009 0010 #include <KCompletion> 0011 #include <KCompletionMatches> 0012 #include <stdlib.h> 0013 0014 class KShellCompletionPrivate 0015 { 0016 public: 0017 KShellCompletionPrivate() 0018 : m_word_break_char(QLatin1Char(' ')) 0019 , m_quote_char1(QLatin1Char('\"')) 0020 , m_quote_char2(QLatin1Char('\'')) 0021 , m_escape_char(QLatin1Char('\\')) 0022 { 0023 } 0024 0025 void splitText(const QString &text, QString &text_start, QString &text_compl) const; 0026 bool quoteText(QString *text, bool force, bool skip_last) const; 0027 QString unquote(const QString &text) const; 0028 0029 QString m_text_start; // part of the text that was not completed 0030 QString m_text_compl; // part of the text that was completed (unchanged) 0031 0032 QChar m_word_break_char; 0033 QChar m_quote_char1; 0034 QChar m_quote_char2; 0035 QChar m_escape_char; 0036 }; 0037 0038 KShellCompletion::KShellCompletion() 0039 : KUrlCompletion() 0040 , d(new KShellCompletionPrivate) 0041 { 0042 } 0043 0044 KShellCompletion::~KShellCompletion() = default; 0045 0046 /* 0047 * makeCompletion() 0048 * 0049 * Entry point for file name completion 0050 */ 0051 QString KShellCompletion::makeCompletion(const QString &text) 0052 { 0053 // Split text at the last unquoted space 0054 // 0055 d->splitText(text, d->m_text_start, d->m_text_compl); 0056 0057 // Remove quotes from the text to be completed 0058 // 0059 QString tmp = d->unquote(d->m_text_compl); 0060 d->m_text_compl = tmp; 0061 0062 // Do exe-completion if there was no unquoted space 0063 // 0064 const bool is_exe_completion = !d->m_text_start.contains(d->m_word_break_char); 0065 0066 setMode(is_exe_completion ? ExeCompletion : FileCompletion); 0067 0068 // Make completion on the last part of text 0069 // 0070 return KUrlCompletion::makeCompletion(d->m_text_compl); 0071 } 0072 0073 /* 0074 * postProcessMatch, postProcessMatches 0075 * 0076 * Called by KCompletion before emitting match() and matches() 0077 * 0078 * Add add the part of the text that was not completed 0079 * Add quotes when needed 0080 */ 0081 void KShellCompletion::postProcessMatch(QString *match) const 0082 { 0083 KUrlCompletion::postProcessMatch(match); 0084 0085 if (match->isNull()) { 0086 return; 0087 } 0088 0089 if (match->endsWith(QLatin1Char('/'))) { 0090 d->quoteText(match, false, true); // don't quote the trailing '/' 0091 } else { 0092 d->quoteText(match, false, false); // quote the whole text 0093 } 0094 0095 match->prepend(d->m_text_start); 0096 } 0097 0098 void KShellCompletion::postProcessMatches(QStringList *matches) const 0099 { 0100 KUrlCompletion::postProcessMatches(matches); 0101 0102 for (QString &match : *matches) { 0103 if (!match.isNull()) { 0104 if (match.endsWith(QLatin1Char('/'))) { 0105 d->quoteText(&match, false, true); // don't quote trailing '/' 0106 } else { 0107 d->quoteText(&match, false, false); // quote the whole text 0108 } 0109 0110 match.prepend(d->m_text_start); 0111 } 0112 } 0113 } 0114 0115 void KShellCompletion::postProcessMatches(KCompletionMatches *matches) const 0116 { 0117 KUrlCompletion::postProcessMatches(matches); 0118 0119 for (auto &match : *matches) { 0120 QString &matchString = match.value(); 0121 if (!matchString.isNull()) { 0122 if (matchString.endsWith(QLatin1Char('/'))) { 0123 d->quoteText(&matchString, false, true); // don't quote trailing '/' 0124 } else { 0125 d->quoteText(&matchString, false, false); // quote the whole text 0126 } 0127 0128 matchString.prepend(d->m_text_start); 0129 } 0130 } 0131 } 0132 0133 /* 0134 * splitText 0135 * 0136 * Split text at the last unquoted space 0137 * 0138 * text_start = [out] text at the left, including the space 0139 * text_compl = [out] text at the right 0140 */ 0141 void KShellCompletionPrivate::splitText(const QString &text, QString &text_start, QString &text_compl) const 0142 { 0143 bool in_quote = false; 0144 bool escaped = false; 0145 QChar p_last_quote_char; 0146 int last_unquoted_space = -1; 0147 0148 for (int pos = 0; pos < text.length(); pos++) { 0149 int end_space_len = 0; 0150 0151 if (escaped) { 0152 escaped = false; 0153 } else if (in_quote && text[pos] == p_last_quote_char) { 0154 in_quote = false; 0155 } else if (!in_quote && text[pos] == m_quote_char1) { 0156 p_last_quote_char = m_quote_char1; 0157 in_quote = true; 0158 } else if (!in_quote && text[pos] == m_quote_char2) { 0159 p_last_quote_char = m_quote_char2; 0160 in_quote = true; 0161 } else if (text[pos] == m_escape_char) { 0162 escaped = true; 0163 } else if (!in_quote && text[pos] == m_word_break_char) { 0164 end_space_len = 1; 0165 0166 while (pos + 1 < text.length() && text[pos + 1] == m_word_break_char) { 0167 end_space_len++; 0168 pos++; 0169 } 0170 0171 if (pos + 1 == text.length()) { 0172 break; 0173 } 0174 0175 last_unquoted_space = pos; 0176 } 0177 } 0178 0179 text_start = text.left(last_unquoted_space + 1); 0180 0181 // the last part without trailing blanks 0182 text_compl = text.mid(last_unquoted_space + 1); 0183 } 0184 0185 /* 0186 * quoteText() 0187 * 0188 * Add quotations to 'text' if needed or if 'force' = true 0189 * Returns true if quotes were added 0190 * 0191 * skip_last => ignore the last character (we add a space or '/' to all filenames) 0192 */ 0193 bool KShellCompletionPrivate::quoteText(QString *text, bool force, bool skip_last) const 0194 { 0195 int pos = 0; 0196 0197 if (!force) { 0198 pos = text->indexOf(m_word_break_char); 0199 if (skip_last && (pos == (int)(text->length()) - 1)) { 0200 pos = -1; 0201 } 0202 } 0203 0204 if (!force && pos == -1) { 0205 pos = text->indexOf(m_quote_char1); 0206 if (skip_last && (pos == (int)(text->length()) - 1)) { 0207 pos = -1; 0208 } 0209 } 0210 0211 if (!force && pos == -1) { 0212 pos = text->indexOf(m_quote_char2); 0213 if (skip_last && (pos == (int)(text->length()) - 1)) { 0214 pos = -1; 0215 } 0216 } 0217 0218 if (!force && pos == -1) { 0219 pos = text->indexOf(m_escape_char); 0220 if (skip_last && (pos == (int)(text->length()) - 1)) { 0221 pos = -1; 0222 } 0223 } 0224 0225 if (force || (pos >= 0)) { 0226 // Escape \ in the string 0227 text->replace(m_escape_char, QString(m_escape_char) + m_escape_char); 0228 0229 // Escape " in the string 0230 text->replace(m_quote_char1, QString(m_escape_char) + m_quote_char1); 0231 0232 // " at the beginning 0233 text->insert(0, m_quote_char1); 0234 0235 // " at the end 0236 if (skip_last) { 0237 text->insert(text->length() - 1, m_quote_char1); 0238 } else { 0239 text->insert(text->length(), m_quote_char1); 0240 } 0241 0242 return true; 0243 } 0244 0245 return false; 0246 } 0247 0248 /* 0249 * unquote 0250 * 0251 * Remove quotes and return the result in a new string 0252 * 0253 */ 0254 QString KShellCompletionPrivate::unquote(const QString &text) const 0255 { 0256 bool in_quote = false; 0257 bool escaped = false; 0258 QChar p_last_quote_char; 0259 QString result; 0260 0261 for (const QChar ch : text) { 0262 if (escaped) { 0263 escaped = false; 0264 result.insert(result.length(), ch); 0265 } else if (in_quote && ch == p_last_quote_char) { 0266 in_quote = false; 0267 } else if (!in_quote && ch == m_quote_char1) { 0268 p_last_quote_char = m_quote_char1; 0269 in_quote = true; 0270 } else if (!in_quote && ch == m_quote_char2) { 0271 p_last_quote_char = m_quote_char2; 0272 in_quote = true; 0273 } else if (ch == m_escape_char) { 0274 escaped = true; 0275 result.insert(result.length(), ch); 0276 } else { 0277 result.insert(result.length(), ch); 0278 } 0279 } 0280 0281 return result; 0282 } 0283 0284 #include "moc_kshellcompletion.cpp"