|
1 // line_completer.cpp |
|
2 // |
|
3 // Copyright (c) 2006 - 2010 Accenture. All rights reserved. |
|
4 // This component and the accompanying materials are made available |
|
5 // under the terms of the "Eclipse Public License v1.0" |
|
6 // which accompanies this distribution, and is available |
|
7 // at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 // |
|
9 // Initial Contributors: |
|
10 // Accenture - Initial contribution |
|
11 // |
|
12 |
|
13 #include <f32file.h> |
|
14 #include <fshell/ltkutils.h> |
|
15 #include "lexer.h" |
|
16 #include "command_factory.h" |
|
17 #include "line_completer.h" |
|
18 |
|
19 _LIT(KTab, "\t"); |
|
20 _LIT(KWild, "*"); |
|
21 _LIT(KDoubleQuote, "\""); |
|
22 _LIT(KSpace, " "); |
|
23 _LIT(KFileNameSlash, "\\"); |
|
24 _LIT(KPipe, "|"); |
|
25 _LIT(KDoublePipe, "||"); |
|
26 _LIT(KAmpersand, "&"); |
|
27 _LIT(KDoubleAmpersand, "&&"); |
|
28 _LIT(KAmpersandPipe, "&|"); |
|
29 _LIT(KSemicolon, ";"); |
|
30 _LIT(KDollar, "$"); |
|
31 _LIT(KDashDash, "--"); |
|
32 _LIT(KDash, "-"); |
|
33 |
|
34 CLineCompleter* CLineCompleter::NewL(RFs& aFs, CCommandFactory& aCommandFactory, IoUtils::CEnvironment& aEnv) |
|
35 { |
|
36 CLineCompleter* self = new(ELeave) CLineCompleter(aFs, aCommandFactory, aEnv); |
|
37 CleanupStack::PushL(self); |
|
38 self->ConstructL(); |
|
39 CleanupStack::Pop(self); |
|
40 return self; |
|
41 } |
|
42 |
|
43 CLineCompleter::~CLineCompleter() |
|
44 { |
|
45 delete iLexer; |
|
46 delete iLastUsedCif; |
|
47 } |
|
48 |
|
49 void CLineCompleter::LcCompleteLineL(TConsoleLine& aLine, const TChar& aEscapeChar) |
|
50 { |
|
51 iLexer->Set(aLine.ContentsToCursor(), aEscapeChar); |
|
52 TToken token(iLexer->NextToken()); |
|
53 TToken firstToken = token; |
|
54 TToken prevToken(TToken::EString, KNullDesC, aLine.ContentsToCursor().Length()); |
|
55 TInt lastTokenIdx = 0; |
|
56 while (iLexer->More()) |
|
57 { |
|
58 // More than one token - skip to last. |
|
59 prevToken = token; |
|
60 token = iLexer->NextToken(); |
|
61 lastTokenIdx++; |
|
62 } |
|
63 |
|
64 switch (prevToken.Type()) |
|
65 { |
|
66 case TToken::EPipe: |
|
67 case TToken::EAmpersand: |
|
68 case TToken::ESemicolon: |
|
69 case TToken::EDoublePipe: |
|
70 case TToken::EDoubleAmpersand: |
|
71 case TToken::EAmpersandPipe: |
|
72 CompleteCommandNameL(aLine, token, aEscapeChar); |
|
73 break; |
|
74 default: |
|
75 if (token.Position() + token.String().Length() < aLine.ContentsToCursor().Length()) |
|
76 { |
|
77 // Looks like there's whitespace after this token, so consider it to be completing the empty token. |
|
78 token = TToken(TToken::EString, KNullDesC, aLine.ContentsToCursor().Length()); |
|
79 } |
|
80 else if (lastTokenIdx == 0) |
|
81 { |
|
82 CompleteCommandNameL(aLine, token, aEscapeChar); |
|
83 break; |
|
84 } |
|
85 |
|
86 if (token.String().Left(1) == KDollar) |
|
87 { |
|
88 CompleteEnvVarL(aLine, token, aEscapeChar); |
|
89 } |
|
90 else if (token.String() == KDash || token.String().Left(2) == KDashDash) |
|
91 { |
|
92 // Check the CIF is loaded and it's the right one |
|
93 if (iLastUsedCif && iLastUsedCif->Name().CompareF(firstToken.String()) != 0) |
|
94 { |
|
95 delete iLastUsedCif; |
|
96 iLastUsedCif = NULL; |
|
97 } |
|
98 TInt err = KErrNone; |
|
99 if (iLastUsedCif == NULL) |
|
100 { |
|
101 // We need to supress StaticLeaveIfErrs from CCommandInfoFile::NewL causing a PrintError if there's no CIF for this command, |
|
102 // hence the use of TRAP_QUIETLY. |
|
103 TRAP_QUIETLY(err, iLastUsedCif = IoUtils::CCommandInfoFile::NewL(iFs, iEnv, firstToken.String())); |
|
104 } |
|
105 if (err == KErrNone) |
|
106 { |
|
107 CompleteOptionL(aLine, token, aEscapeChar); |
|
108 } |
|
109 } |
|
110 else |
|
111 { |
|
112 CompleteFileNameL(aLine, token, aEscapeChar); |
|
113 } |
|
114 break; |
|
115 } |
|
116 } |
|
117 |
|
118 CLineCompleter::CLineCompleter(RFs& aFs, CCommandFactory& aCommandFactory, IoUtils::CEnvironment& aEnv) |
|
119 : iFs(aFs), iCommandFactory(aCommandFactory), iEnv(aEnv) |
|
120 { |
|
121 } |
|
122 |
|
123 void CLineCompleter::ConstructL() |
|
124 { |
|
125 iLexer = CLexer::NewL(); |
|
126 iLexer->DefineTokenTypeL(TToken::EPipe, KPipe); |
|
127 iLexer->DefineTokenTypeL(TToken::EAmpersand, KAmpersand); |
|
128 iLexer->DefineTokenTypeL(TToken::ESemicolon, KSemicolon); |
|
129 iLexer->DefineTokenTypeL(TToken::EDoublePipe, KDoublePipe); |
|
130 iLexer->DefineTokenTypeL(TToken::EDoubleAmpersand, KDoubleAmpersand); |
|
131 iLexer->DefineTokenTypeL(TToken::EAmpersandPipe, KAmpersandPipe); |
|
132 } |
|
133 |
|
134 HBufC* SimplifyStringLC(const TDesC& aString, const TChar& aEscapeChar) |
|
135 { |
|
136 HBufC* buf = HBufC::NewLC(aString.Length()); |
|
137 TPtr bufPtr(buf->Des()); |
|
138 TLex lex(aString); |
|
139 while (!lex.Eos()) |
|
140 { |
|
141 TChar c = lex.Get(); |
|
142 if (c == aEscapeChar) |
|
143 { |
|
144 if (!lex.Eos()) |
|
145 { |
|
146 bufPtr.Append(lex.Get()); |
|
147 } |
|
148 } |
|
149 else if (c == '\'') |
|
150 { |
|
151 while (!lex.Eos()) |
|
152 { |
|
153 c = lex.Get(); |
|
154 if (c == '\'') |
|
155 { |
|
156 break; |
|
157 } |
|
158 else |
|
159 { |
|
160 bufPtr.Append(c); |
|
161 } |
|
162 } |
|
163 } |
|
164 else if (c == '"') |
|
165 { |
|
166 while (!lex.Eos()) |
|
167 { |
|
168 c = lex.Get(); |
|
169 if (c == aEscapeChar) |
|
170 { |
|
171 bufPtr.Append(lex.Get()); |
|
172 } |
|
173 else if (c == '"') |
|
174 { |
|
175 break; |
|
176 } |
|
177 else |
|
178 { |
|
179 bufPtr.Append(c); |
|
180 } |
|
181 } |
|
182 } |
|
183 else |
|
184 { |
|
185 bufPtr.Append(c); |
|
186 } |
|
187 } |
|
188 return buf; |
|
189 } |
|
190 |
|
191 void CLineCompleter::CompleteCommandNameL(TConsoleLine& aLine, const TToken& aToken, const TChar& aEscapeChar) const |
|
192 { |
|
193 RArray<TPtrC> commands; |
|
194 CleanupClosePushL(commands); |
|
195 iCommandFactory.ListCommandsL(commands); |
|
196 HBufC* commandBuf = SimplifyStringLC(aToken.String(), aEscapeChar); |
|
197 |
|
198 // Remove commands that don't match aLine. |
|
199 TInt i; |
|
200 for (i = (commands.Count() - 1); i >= 0; --i) |
|
201 { |
|
202 if (commands[i].Find(*commandBuf) != 0) |
|
203 { |
|
204 commands.Remove(i); |
|
205 } |
|
206 } |
|
207 |
|
208 if (commands.Count() > 0) |
|
209 { |
|
210 CompleteL(aLine, aToken, commands, NULL, NULL); |
|
211 } |
|
212 else |
|
213 { |
|
214 CompleteFileNameL(aLine, aToken, aEscapeChar); |
|
215 } |
|
216 CleanupStack::PopAndDestroy(2, &commands); |
|
217 } |
|
218 |
|
219 void RemovePartialElement(HBufC*& aBuf) |
|
220 { |
|
221 TPtr ptr(aBuf->Des()); |
|
222 while (ptr.Length() > 0) |
|
223 { |
|
224 if (ptr[ptr.Length() - 1] == KPathDelimiter) |
|
225 { |
|
226 break; |
|
227 } |
|
228 else |
|
229 { |
|
230 ptr.Delete(ptr.Length() - 1, 1); |
|
231 } |
|
232 } |
|
233 } |
|
234 |
|
235 void CLineCompleter::CompleteFileNameL(TConsoleLine& aLine, const TToken& aToken, const TChar& aEscapeChar) const |
|
236 { |
|
237 HBufC* fileNameBuf = SimplifyStringLC(aToken.String(), aEscapeChar); |
|
238 IoUtils::TFileName2 absFileName(*fileNameBuf); |
|
239 RemovePartialElement(fileNameBuf); |
|
240 absFileName.Append(KWild); |
|
241 absFileName.MakeAbsoluteL(iEnv.Pwd()); |
|
242 CDir* files; |
|
243 User::LeaveIfError(iFs.GetDir(absFileName, KEntryAttNormal | KEntryAttDir, ESortByName, files)); |
|
244 CleanupStack::PushL(files); |
|
245 RArray<TPtrC> fileNames; |
|
246 CleanupClosePushL(fileNames); |
|
247 const TInt numFiles = files->Count(); |
|
248 if (numFiles == 1) |
|
249 { |
|
250 absFileName = absFileName.DriveAndPath(); |
|
251 absFileName.AppendComponentL((*files)[0].iName); |
|
252 TEntry entry; |
|
253 User::LeaveIfError(iFs.Entry(absFileName, entry)); |
|
254 User::LeaveIfError(fileNames.Append(absFileName.NameAndExt())); |
|
255 CompleteL(aLine, aToken, fileNames, fileNameBuf, entry.IsDir() ? &KFileNameSlash : NULL); |
|
256 } |
|
257 else if (numFiles > 1) |
|
258 { |
|
259 for (TInt i = 0; i < numFiles; ++i) |
|
260 { |
|
261 User::LeaveIfError(fileNames.Append(TPtrC((*files)[i].iName))); |
|
262 } |
|
263 CompleteL(aLine, aToken, fileNames, fileNameBuf, NULL); |
|
264 } |
|
265 CleanupStack::PopAndDestroy(3, fileNameBuf); |
|
266 } |
|
267 |
|
268 TBool NeedsQuoting(const TDesC& aDes) |
|
269 { |
|
270 return ((aDes.Locate(TChar(' ')) >= 0) || aDes.Locate(TChar('\'')) >= 0); |
|
271 } |
|
272 |
|
273 void CLineCompleter::CompleteL(TConsoleLine& aLine, const TToken& aToken, const RArray<TPtrC> aPossibilities, const TDesC* aPrefix, const TDesC* aSuffix, TBool aPrefixIsPartOfToken) const |
|
274 { |
|
275 const TInt numPossibilities = aPossibilities.Count(); |
|
276 |
|
277 if (numPossibilities > 1) |
|
278 { |
|
279 // Fill out possibilities buffer. |
|
280 IoUtils::CTextBuffer* possibilities = IoUtils::CTextBuffer::NewLC(0x100); |
|
281 for (TInt i = 0; i < numPossibilities; ++i) |
|
282 { |
|
283 if (aPrefixIsPartOfToken) possibilities->AppendL(*aPrefix); |
|
284 possibilities->AppendL(aPossibilities[i]); |
|
285 if (i != (numPossibilities - 1)) |
|
286 { |
|
287 possibilities->AppendL(KTab); |
|
288 } |
|
289 } |
|
290 |
|
291 aLine.PrintCompletionPossibilitiesL(possibilities->Descriptor()); |
|
292 CleanupStack::PopAndDestroy(possibilities); |
|
293 } |
|
294 |
|
295 if (numPossibilities > 0) |
|
296 { |
|
297 IoUtils::CTextBuffer* completion = IoUtils::CTextBuffer::NewLC(0x100); |
|
298 TPtrC commonChars(NULL, 0); |
|
299 if (numPossibilities > 1) |
|
300 { |
|
301 // Find common leading characters of the possibilities. |
|
302 TInt commonCharPos = -1; |
|
303 TBool finished(EFalse); |
|
304 do |
|
305 { |
|
306 ++commonCharPos; |
|
307 TChar character(0); |
|
308 for (TInt i = 0; i < numPossibilities; ++i) |
|
309 { |
|
310 if (commonCharPos >= aPossibilities[i].Length()) |
|
311 { |
|
312 finished = ETrue; |
|
313 break; |
|
314 } |
|
315 else if (i == 0) |
|
316 { |
|
317 character = aPossibilities[0][commonCharPos]; |
|
318 character.Fold(); |
|
319 } |
|
320 else |
|
321 { |
|
322 TChar c(aPossibilities[i][commonCharPos]); |
|
323 c.Fold(); |
|
324 if (c != character) |
|
325 { |
|
326 finished = ETrue; |
|
327 break; |
|
328 } |
|
329 } |
|
330 } |
|
331 } |
|
332 while (!finished); |
|
333 |
|
334 commonChars.Set(aPossibilities[0].Mid(0, commonCharPos)); |
|
335 } |
|
336 else |
|
337 { |
|
338 commonChars.Set(aPossibilities[0]); |
|
339 } |
|
340 |
|
341 TBool quote(EFalse); |
|
342 if ((aPrefix && NeedsQuoting(*aPrefix)) || NeedsQuoting(commonChars) || (aSuffix && NeedsQuoting(*aSuffix))) |
|
343 { |
|
344 quote = ETrue; |
|
345 } |
|
346 if (quote) |
|
347 { |
|
348 completion->AppendL(KDoubleQuote); |
|
349 } |
|
350 if (aPrefix) |
|
351 { |
|
352 completion->AppendL(*aPrefix); |
|
353 } |
|
354 completion->AppendL(commonChars); |
|
355 if ((numPossibilities == 1) && quote) |
|
356 { |
|
357 completion->AppendL(KDoubleQuote); |
|
358 } |
|
359 if (numPossibilities == 1) |
|
360 { |
|
361 if (aSuffix) |
|
362 { |
|
363 completion->AppendL(*aSuffix); |
|
364 } |
|
365 else |
|
366 { |
|
367 completion->AppendL(KSpace); |
|
368 } |
|
369 } |
|
370 if (completion->Descriptor().Length() > 0) |
|
371 { |
|
372 aLine.Replace(aToken.Position(), completion->Descriptor()); |
|
373 } |
|
374 CleanupStack::PopAndDestroy(completion); |
|
375 } |
|
376 } |
|
377 |
|
378 void CLineCompleter::CompleteEnvVarL(TConsoleLine& aLine, const TToken& aToken, const TChar& /*aEscapeChar*/) const |
|
379 { |
|
380 RArray<TPtrC> matchingVars; |
|
381 CleanupClosePushL(matchingVars); |
|
382 TPtrC envVar = aToken.String().Mid(1); // Lose the $ |
|
383 |
|
384 RPointerArray<HBufC> keys; |
|
385 LtkUtils::CleanupResetAndDestroyPushL(keys); |
|
386 iEnv.GetKeysL(keys); |
|
387 |
|
388 const TInt count = keys.Count(); |
|
389 for (TInt i = 0; i < count; i++) |
|
390 { |
|
391 const TDesC& key = *keys[i]; |
|
392 if (key.Left(envVar.Length()) == envVar) |
|
393 { |
|
394 matchingVars.AppendL(TPtrC(key)); |
|
395 } |
|
396 } |
|
397 CompleteL(aLine, aToken, matchingVars, &KDollar, NULL, ETrue); |
|
398 CleanupStack::PopAndDestroy(2, &matchingVars); // keys, matchingVars |
|
399 } |
|
400 |
|
401 void CLineCompleter::CompleteOptionL(TConsoleLine& aLine, const TToken& aToken, const TChar& /*aEscapeChar*/) const |
|
402 { |
|
403 RArray<TPtrC> matchingOptions; |
|
404 CleanupClosePushL(matchingOptions); |
|
405 TPtrC optFrag = aToken.String(); |
|
406 if (optFrag.Length() >= 2) optFrag.Set(optFrag.Mid(2)); // Lose the -- |
|
407 else if (optFrag.Length() == 1) optFrag.Set(TPtrC()); // single dash gets dropped completely |
|
408 |
|
409 const IoUtils::RCommandOptionList& options = iLastUsedCif->Options(); |
|
410 const TInt count = options.Count(); |
|
411 for (TInt i = 0; i < count; i++) |
|
412 { |
|
413 const TDesC& optName = options[i].Name();; |
|
414 if (optName.Left(optFrag.Length()) == optFrag) |
|
415 { |
|
416 matchingOptions.AppendL(TPtrC(optName)); |
|
417 } |
|
418 } |
|
419 CompleteL(aLine, aToken, matchingOptions, &KDashDash, NULL, ETrue); |
|
420 CleanupStack::PopAndDestroy(&matchingOptions); |
|
421 } |
|
422 |
|
423 |