summaryrefslogtreecommitdiffstats
path: root/src/symbolcompletion.cpp
blob: ee603b4a3578e894987cc53e1c6835d49ef56f58 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
/***************************************************************************
 *
 * Copyright (C) 2005 Elad Lahav (elad_lahav@users.sourceforge.net)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ***************************************************************************/

#include <tqtimer.h>
#include <tdelocale.h>
#include "symbolcompletion.h"

bool SymbolCompletion::s_bACEnabled;
uint SymbolCompletion::s_nACMinChars;
uint SymbolCompletion::s_nACDelay;
uint SymbolCompletion::s_nACMaxEntries;

/**
 * Class constructor.
 * @param	pEditor	The editor object for which symbol completion is required
 * @param	pParent	Parent object
 * @param	szName	Optional object name
 */
SymbolCompletion::SymbolCompletion(SymbolCompletion::Interface* pEditor, 
	TQObject* pParent, const char* szName) :
 	TQObject(pParent, szName),
	m_pEditor(pEditor),
	m_pCCObject(NULL)
{
	// Initialise member objects
	m_pCscope = new CscopeFrontend();
	m_pAutoCompTimer = new TQTimer(this);
	
	// Add entries to the completion list when they are available
	connect(m_pCscope, SIGNAL(dataReady(FrontendToken*)), this, 
		SLOT(slotAddEntry(FrontendToken*)));
	
	// Show the completion list when the query finishes
	connect(m_pCscope, SIGNAL(finished(uint)), this, 
		SLOT(slotQueryFinished(uint)));
			
	// Initiate automatic symbol completion when timer expires
	connect(m_pAutoCompTimer, SIGNAL(timeout()), this,
		SLOT(slotAutoCompleteTimeout()));
}

/**
 * Class destructor.
 */
SymbolCompletion::~SymbolCompletion()
{
	delete m_pCscope;
}

/**
 * Stops a completion process.
 * This includes killing a running query, and stoping the auto-completion
 * timer.
 */
void SymbolCompletion::abort()
{
	if (m_pCscope->isRunning())
		m_pCscope->kill();
		
	m_pAutoCompTimer->stop();
}

/**
 * Configures auto-completion parameters.
 * @param	bEnabled	true to enable auto-completion, false otherwise
 * @param	nMinChars	Minimal number of characters a symbol needs to start
 *						auto-completion
 * @param	nDelay		Auto-completion time interval (in milliseconds)
 * @param	nMaxEntries	The maximal number of completion entries
 */
void SymbolCompletion::initAutoCompletion(bool bEnabled, uint nMinChars, 
	uint nDelay, uint nMaxEntries)
{
	s_bACEnabled = bEnabled;
	s_nACMinChars = nMinChars;
	s_nACDelay = nDelay;
	s_nACMaxEntries = nMaxEntries;
}

/**
 * Starts a completion process immediately for the symbol currently under the
 * cursor in the editor object.
 * Symbol completion is only available if the cursor is positioned at the end
 * of the symbol.
 */
void SymbolCompletion::slotComplete()
{
	TQString sSymbol;
	uint nPosInWord;
	
	// Read the symbol currently under the cursor
	sSymbol = m_pEditor->getWordUnderCursor(&nPosInWord);
	
	// The completion was triggered by user
	m_bAutoCompletion = false;
	
	// start completion process, prefix is only on the left from the cursor
	complete(sSymbol.left(nPosInWord));
}

/**
 * Initiates an auto-completion timer.
 * When the timer times-out, is starts the symbol completion process.
 */
void SymbolCompletion::slotAutoComplete()
{
	if (s_bACEnabled)
		m_pAutoCompTimer->start(s_nACDelay, true);
}

/**
 * Creates a list of possible completions to the symbol currently being
 * edited.
 * @param	sPrefix		The symbol to complete
 * @param	nMaxEntries	The maximal number of entries to display
 */
void SymbolCompletion::complete(const TQString& sPrefix, int nMaxEntries)
{	
	// Create a regular expression to extract symbol names from the query
	// results
	m_reSymbol.setPattern(sPrefix + "[a-zA-Z0-9_]*");
	
	// If the new prefix is itself a prefix of the old one, we only need to
	// filter the current entries
	if (!m_sPrefix.isEmpty() && sPrefix.startsWith(m_sPrefix)) {
		filterEntries();
		m_sPrefix = sPrefix;
		slotQueryFinished(0);
		return;
	}
	
	// Prepare member variables
	m_sPrefix = sPrefix;
	m_nMaxEntries = nMaxEntries;
	m_elEntries.clear();
		
	// Run the code-completion query 
	m_pCscope->query(CscopeFrontend::Definition, sPrefix + ".*");
}

/**
 * Removes from the current completion list all symbols that do not match
 * the current regular expression.
 * This function is used to aviod requerying the database on certain
 * situations.
 */
void SymbolCompletion::filterEntries()
{
	EntryList::Iterator itr;
	
	// Iterate over the list and check each entry against the current RE
	for (itr = m_elEntries.begin(); itr != m_elEntries.end();) {
		if (m_reSymbol.search((*itr).text) == -1)
			itr = m_elEntries.erase(itr);
		else
			++itr;
	}
}

/**
 * Conevrts the completion list into a single-entry one, containing the given
 * message.
 * @param	sMsg	The text of the message to include in the list.
 */
void SymbolCompletion::makeErrMsg(const TQString& sMsg)
{
	Entry entry;
	
	// Clear the current list
	m_elEntries.clear();
	
	// Create the message item and add it to the list
	entry.text = sMsg;
	entry.userdata = "NO_INSERT"; // The message should not be insertable
	m_elEntries.append(entry);
	
	// Make sure a new completion request will start a new query
	m_sPrefix = "";
}

/**
 * Creates a new entry in the list when a query record is available.
 * This slot is connected to the dataReady() signal of the CscopeFrontend
 * object.
 * @param	pToken	Points to the head of a record's linked-list
 */
void SymbolCompletion::slotAddEntry(FrontendToken* pToken)
{
	Entry entry;
	TQString sText;
	
	// Do not add entries beyond the requested limit
	if (m_elEntries.count() > m_nMaxEntries)
		return;
	
	// Get the line text
	pToken = pToken->getNext()->getNext()->getNext();
	sText = pToken->getData();

	// Find the symbol within the line
	if (m_reSymbol.search(sText) == -1)
		return;

	// Add the new entry to the completion list	
	entry.text = m_reSymbol.capturedTexts().first();
	entry.userdata = "";
	entry.comment = sText;
	
	m_elEntries.append(entry);
}

/**
 * Displays a code completion list, based on the results of the last query.
 * @param	nRecords	(ingnored)
 */
void SymbolCompletion::slotQueryFinished(uint /* nRecords */)
{
	KTextEditor::CodeCompletionInterface* pCCI;
	uint nEntryCount;
	EntryList::Iterator itr;
	TQString sPrevText;
	
	// Get the number of entries
	nEntryCount = m_elEntries.count();
	
	// Do not show the box the only completion option is the prefix itself
	if (m_bAutoCompletion && (nEntryCount == 1) &&
		(m_elEntries.first().text == m_sPrefix)) {
		return;
	}
	
	// Get a pointer to the CC interface
	m_pCCObject = m_pEditor->getCCObject();
	pCCI = dynamic_cast<KTextEditor::CodeCompletionInterface*>(m_pCCObject);
	if (!pCCI)
		return;
		
	// Insert the correct part of the completed symbol, when chosen by the
	// user
	connect(m_pCCObject, 
		SIGNAL(filterInsertString(KTextEditor::CompletionEntry*, TQString*)),
		this, 
		SLOT(slotFilterInsert(KTextEditor::CompletionEntry*, TQString*)));

	// Check the number of entries in the list	
	if (nEntryCount == 0) {
		// No completion options, display an appropriate message
		makeErrMsg(i18n("No matching completion found..."));
	}
	else if (nEntryCount > m_nMaxEntries) {
		// The query has resulted in too many entries, display an
		// appropriate message
		makeErrMsg(i18n("Too many options..."));
	}
	else {
		// Sort the entries
		m_elEntries.sort();
		
		// Make sure entries are unique
		for (itr = m_elEntries.begin(); itr != m_elEntries.end();) {
			if ((*itr).text == sPrevText) {
				itr = m_elEntries.erase(itr);
			}
			else {
				sPrevText = (*itr).text;
				++itr;
			}
		}
	}
	
	// Display the completion list
	pCCI->showCompletionBox(m_elEntries);
}

/**
 * Determines which part of the completion entry should be added to the code
 * when that entry is selected.
 * @param	pEntry			Points to the selected entry
 * @param	pTextToInsert	Contains the string to insert, upon return
 */
void SymbolCompletion::slotFilterInsert(KTextEditor::CompletionEntry* pEntry, 
	TQString* pTextToInsert)
{
	// Insert the completed entry, unless it contains an error message
	if (pEntry->userdata.isEmpty())
		*pTextToInsert = pEntry->text.mid(m_sPrefix.length());
	else
		*pTextToInsert = "";
		
	// Disconnect the CC object signals
	disconnect(m_pCCObject, 0, this, 0);
	m_pCCObject = NULL;
}

/**
 * Checks if the current symbol is eligible for auto-completion, and if so,
 * starts the completion process.
 * Auto-completion is performed for symbols that have the required minimal
 * number of entries, and the cursor is positioned at the end of the word.
 * This slot is connected to the timeout() signal of the auto-completion
 * timer.
 */
void SymbolCompletion::slotAutoCompleteTimeout()
{	
	TQString sPrefix;
	uint nPosInWord, nLength;
	
	// Read the symbol currently under the cursor
	sPrefix = m_pEditor->getWordUnderCursor(&nPosInWord);
	nLength = sPrefix.length();
	
	// Check conditions, and start the completion process
	if ((nLength >= s_nACMinChars) && (nPosInWord == nLength)) {
		// The completion was triggered by auto-completion
		m_bAutoCompletion = true;
		complete(sPrefix, s_nACMaxEntries);
	}
}

#include "symbolcompletion.moc"