KIO

kshellcompletion.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2000 David Smith <[email protected]>
4 
5  SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "kshellcompletion.h"
9 
10 #include <KCompletion>
11 #include <KCompletionMatches>
12 #include <stdlib.h>
13 
14 class KShellCompletionPrivate
15 {
16 public:
17  KShellCompletionPrivate()
18  : m_word_break_char(QLatin1Char(' '))
19  , m_quote_char1(QLatin1Char('\"'))
20  , m_quote_char2(QLatin1Char('\''))
21  , m_escape_char(QLatin1Char('\\'))
22  {
23  }
24 
25  void splitText(const QString &text, QString &text_start, QString &text_compl) const;
26  bool quoteText(QString *text, bool force, bool skip_last) const;
27  QString unquote(const QString &text) const;
28 
29  QString m_text_start; // part of the text that was not completed
30  QString m_text_compl; // part of the text that was completed (unchanged)
31 
32  QChar m_word_break_char;
33  QChar m_quote_char1;
34  QChar m_quote_char2;
35  QChar m_escape_char;
36 };
37 
39  : KUrlCompletion()
40  , d(new KShellCompletionPrivate)
41 {
42 }
43 
44 KShellCompletion::~KShellCompletion() = default;
45 
46 /*
47  * makeCompletion()
48  *
49  * Entry point for file name completion
50  */
52 {
53  // Split text at the last unquoted space
54  //
55  d->splitText(text, d->m_text_start, d->m_text_compl);
56 
57  // Remove quotes from the text to be completed
58  //
59  QString tmp = d->unquote(d->m_text_compl);
60  d->m_text_compl = tmp;
61 
62  // Do exe-completion if there was no unquoted space
63  //
64  const bool is_exe_completion = !d->m_text_start.contains(d->m_word_break_char);
65 
66  setMode(is_exe_completion ? ExeCompletion : FileCompletion);
67 
68  // Make completion on the last part of text
69  //
70  return KUrlCompletion::makeCompletion(d->m_text_compl);
71 }
72 
73 /*
74  * postProcessMatch, postProcessMatches
75  *
76  * Called by KCompletion before emitting match() and matches()
77  *
78  * Add add the part of the text that was not completed
79  * Add quotes when needed
80  */
81 void KShellCompletion::postProcessMatch(QString *match) const
82 {
83  KUrlCompletion::postProcessMatch(match);
84 
85  if (match->isNull()) {
86  return;
87  }
88 
89  if (match->endsWith(QLatin1Char('/'))) {
90  d->quoteText(match, false, true); // don't quote the trailing '/'
91  } else {
92  d->quoteText(match, false, false); // quote the whole text
93  }
94 
95  match->prepend(d->m_text_start);
96 }
97 
98 void KShellCompletion::postProcessMatches(QStringList *matches) const
99 {
100  KUrlCompletion::postProcessMatches(matches);
101 
102  for (QString &match : *matches) {
103  if (!match.isNull()) {
104  if (match.endsWith(QLatin1Char('/'))) {
105  d->quoteText(&match, false, true); // don't quote trailing '/'
106  } else {
107  d->quoteText(&match, false, false); // quote the whole text
108  }
109 
110  match.prepend(d->m_text_start);
111  }
112  }
113 }
114 
115 void KShellCompletion::postProcessMatches(KCompletionMatches *matches) const
116 {
117  KUrlCompletion::postProcessMatches(matches);
118 
119  for (auto &match : *matches) {
120  QString &matchString = match.value();
121  if (!matchString.isNull()) {
122  if (matchString.endsWith(QLatin1Char('/'))) {
123  d->quoteText(&matchString, false, true); // don't quote trailing '/'
124  } else {
125  d->quoteText(&matchString, false, false); // quote the whole text
126  }
127 
128  matchString.prepend(d->m_text_start);
129  }
130  }
131 }
132 
133 /*
134  * splitText
135  *
136  * Split text at the last unquoted space
137  *
138  * text_start = [out] text at the left, including the space
139  * text_compl = [out] text at the right
140  */
141 void KShellCompletionPrivate::splitText(const QString &text, QString &text_start, QString &text_compl) const
142 {
143  bool in_quote = false;
144  bool escaped = false;
145  QChar p_last_quote_char;
146  int last_unquoted_space = -1;
147 
148  for (int pos = 0; pos < text.length(); pos++) {
149  int end_space_len = 0;
150 
151  if (escaped) {
152  escaped = false;
153  } else if (in_quote && text[pos] == p_last_quote_char) {
154  in_quote = false;
155  } else if (!in_quote && text[pos] == m_quote_char1) {
156  p_last_quote_char = m_quote_char1;
157  in_quote = true;
158  } else if (!in_quote && text[pos] == m_quote_char2) {
159  p_last_quote_char = m_quote_char2;
160  in_quote = true;
161  } else if (text[pos] == m_escape_char) {
162  escaped = true;
163  } else if (!in_quote && text[pos] == m_word_break_char) {
164  end_space_len = 1;
165 
166  while (pos + 1 < text.length() && text[pos + 1] == m_word_break_char) {
167  end_space_len++;
168  pos++;
169  }
170 
171  if (pos + 1 == text.length()) {
172  break;
173  }
174 
175  last_unquoted_space = pos;
176  }
177  }
178 
179  text_start = text.left(last_unquoted_space + 1);
180 
181  // the last part without trailing blanks
182  text_compl = text.mid(last_unquoted_space + 1);
183 }
184 
185 /*
186  * quoteText()
187  *
188  * Add quotations to 'text' if needed or if 'force' = true
189  * Returns true if quotes were added
190  *
191  * skip_last => ignore the last character (we add a space or '/' to all filenames)
192  */
193 bool KShellCompletionPrivate::quoteText(QString *text, bool force, bool skip_last) const
194 {
195  int pos = 0;
196 
197  if (!force) {
198  pos = text->indexOf(m_word_break_char);
199  if (skip_last && (pos == (int)(text->length()) - 1)) {
200  pos = -1;
201  }
202  }
203 
204  if (!force && pos == -1) {
205  pos = text->indexOf(m_quote_char1);
206  if (skip_last && (pos == (int)(text->length()) - 1)) {
207  pos = -1;
208  }
209  }
210 
211  if (!force && pos == -1) {
212  pos = text->indexOf(m_quote_char2);
213  if (skip_last && (pos == (int)(text->length()) - 1)) {
214  pos = -1;
215  }
216  }
217 
218  if (!force && pos == -1) {
219  pos = text->indexOf(m_escape_char);
220  if (skip_last && (pos == (int)(text->length()) - 1)) {
221  pos = -1;
222  }
223  }
224 
225  if (force || (pos >= 0)) {
226  // Escape \ in the string
227  text->replace(m_escape_char, QString(m_escape_char) + m_escape_char);
228 
229  // Escape " in the string
230  text->replace(m_quote_char1, QString(m_escape_char) + m_quote_char1);
231 
232  // " at the beginning
233  text->insert(0, m_quote_char1);
234 
235  // " at the end
236  if (skip_last) {
237  text->insert(text->length() - 1, m_quote_char1);
238  } else {
239  text->insert(text->length(), m_quote_char1);
240  }
241 
242  return true;
243  }
244 
245  return false;
246 }
247 
248 /*
249  * unquote
250  *
251  * Remove quotes and return the result in a new string
252  *
253  */
254 QString KShellCompletionPrivate::unquote(const QString &text) const
255 {
256  bool in_quote = false;
257  bool escaped = false;
258  QChar p_last_quote_char;
259  QString result;
260 
261  for (const QChar ch : text) {
262  if (escaped) {
263  escaped = false;
264  result.insert(result.length(), ch);
265  } else if (in_quote && ch == p_last_quote_char) {
266  in_quote = false;
267  } else if (!in_quote && ch == m_quote_char1) {
268  p_last_quote_char = m_quote_char1;
269  in_quote = true;
270  } else if (!in_quote && ch == m_quote_char2) {
271  p_last_quote_char = m_quote_char2;
272  in_quote = true;
273  } else if (ch == m_escape_char) {
274  escaped = true;
275  result.insert(result.length(), ch);
276  } else {
277  result.insert(result.length(), ch);
278  }
279  }
280 
281  return result;
282 }
283 
284 #include "moc_kshellcompletion.cpp"
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool isNull() const const
QString & prepend(QChar ch)
QString makeCompletion(const QString &text) override
Finds completions to the given text.
void match(const QString &item)
Completion of a single URL.
void matches(const QStringList &matchlist)
int length() const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString makeCompletion(const QString &text) override
Finds completions to the given text.
QString & replace(int position, int n, QChar after)
virtual void setMode(Mode mode)
Changes the completion mode: exe or file completion.
QString & insert(int position, QChar ch)
QString left(int n) const const
KShellCompletion()
Constructs a KShellCompletion object.
QString mid(int position, int n) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Dec 11 2023 03:50:14 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.