xref: /glogg/src/quickfind.cpp (revision e85b29cce42703b1d8c343be8899be62c386c340)
1 /*
2  * Copyright (C) 2010, 2013, 2017 Nicolas Bonnefon and other contributors
3  *
4  * This file is part of glogg.
5  *
6  * glogg is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * glogg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with glogg.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 // This file implements QuickFind.
21 // This class implements the Quick Find mechanism using references
22 // to the logData, the QFP and the selection passed.
23 // Search is started just after the selection and the selection is updated
24 // if a match is found.
25 
26 #include <QApplication>
27 
28 #include "log.h"
29 #include "quickfindpattern.h"
30 #include "selection.h"
31 #include "data/abstractlogdata.h"
32 
33 #include "quickfind.h"
34 
reset()35 void SearchingNotifier::reset()
36 {
37     dotToDisplay_ = 0;
38     startTime_ = QTime::currentTime();
39 }
40 
sendNotification(qint64 current_line,qint64 nb_lines)41 void SearchingNotifier::sendNotification( qint64 current_line, qint64 nb_lines )
42 {
43     qint64 progress;
44     if ( current_line < 0 )
45         progress = ( nb_lines + current_line ) * 100 / nb_lines;
46     else
47         progress = current_line * 100 / nb_lines;
48     emit notify( QFNotificationProgress( progress ) );
49 
50     QApplication::processEvents( QEventLoop::ExcludeUserInputEvents );
51     startTime_ = QTime::currentTime();
52 }
53 
set(int line,int column)54 void QuickFind::LastMatchPosition::set( int line, int column )
55 {
56     if ( ( line_ == -1 ) ||
57             ( ( line <= line_ ) && ( column < column_ ) ) )
58     {
59         line_ = line;
60         column_ = column;
61     }
62 }
63 
set(const FilePosition & position)64 void QuickFind::LastMatchPosition::set( const FilePosition &position )
65 {
66     set( position.line(), position.column() );
67 }
68 
isLater(int line,int column) const69 bool QuickFind::LastMatchPosition::isLater( int line, int column ) const
70 {
71     if ( line_ == -1 )
72         return false;
73     else if ( ( line == line_ ) && ( column >= column_ ) )
74         return true;
75     else if ( line > line_ )
76         return true;
77     else
78         return false;
79 }
80 
isLater(const FilePosition & position) const81 bool QuickFind::LastMatchPosition::isLater( const FilePosition &position ) const
82 {
83     return isLater( position.line(), position.column() );
84 }
85 
isSooner(int line,int column) const86 bool QuickFind::LastMatchPosition::isSooner( int line, int column ) const
87 {
88     if ( line_ == -1 )
89         return false;
90     else if ( ( line == line_ ) && ( column <= column_ ) )
91         return true;
92     else if ( line < line_ )
93         return true;
94     else
95         return false;
96 }
97 
isSooner(const FilePosition & position) const98 bool QuickFind::LastMatchPosition::isSooner( const FilePosition &position ) const
99 {
100     return isSooner( position.line(), position.column() );
101 }
102 
QuickFind(const AbstractLogData * const logData,Selection * selection,const QuickFindPattern * const quickFindPattern)103 QuickFind::QuickFind( const AbstractLogData* const logData,
104         Selection* selection,
105         const QuickFindPattern* const quickFindPattern ) :
106     logData_( logData ), selection_( selection ),
107     quickFindPattern_( quickFindPattern ),
108     lastMatch_(), firstMatch_(), searchingNotifier_(),
109     incrementalSearchStatus_()
110 {
111     connect( &searchingNotifier_, SIGNAL( notify( const QFNotification& ) ),
112             this, SIGNAL( notify( const QFNotification& ) ) );
113 }
114 
incrementalSearchStop()115 void QuickFind::incrementalSearchStop()
116 {
117     if ( incrementalSearchStatus_.isOngoing() ) {
118         if ( selection_->isEmpty() ) {
119             // Nothing found?
120             // We reset the selection to what it was
121             *selection_ = incrementalSearchStatus_.initialSelection();
122         }
123 
124         incrementalSearchStatus_ = IncrementalSearchStatus();
125     }
126 }
127 
incrementalSearchAbort()128 void QuickFind::incrementalSearchAbort()
129 {
130     if ( incrementalSearchStatus_.isOngoing() ) {
131         // We reset the selection to what it was
132         *selection_ = incrementalSearchStatus_.initialSelection();
133         incrementalSearchStatus_ = IncrementalSearchStatus();
134     }
135 }
136 
incrementallySearchForward()137 qint64 QuickFind::incrementallySearchForward()
138 {
139     LOG( logDEBUG ) << "QuickFind::incrementallySearchForward";
140 
141     // Position where we start the search from
142     FilePosition start_position = selection_->getNextPosition();
143 
144     if ( incrementalSearchStatus_.direction() == Forward ) {
145         // An incremental search is active, we restart the search
146         // from the initial point
147         LOG( logDEBUG ) << "Restart search from initial point";
148         start_position = incrementalSearchStatus_.position();
149     }
150     else {
151         // It's a new search so we search from the selection
152         incrementalSearchStatus_ = IncrementalSearchStatus(
153                 Forward,
154                 start_position,
155                 *selection_ );
156     }
157 
158     qint64 line_found = doSearchForward( start_position );
159 
160     if ( line_found >= 0 ) {
161         // We have found a result...
162         // ... the caller will jump to this line.
163         return line_found;
164     }
165     else {
166         // No result...
167         // ... we want the client to show the initial line.
168         selection_->clear();
169         return incrementalSearchStatus_.position().line();
170     }
171 }
172 
incrementallySearchBackward()173 qint64 QuickFind::incrementallySearchBackward()
174 {
175     LOG( logDEBUG ) << "QuickFind::incrementallySearchBackward";
176 
177     // Position where we start the search from
178     FilePosition start_position = selection_->getPreviousPosition();
179 
180     if ( incrementalSearchStatus_.direction() == Backward ) {
181         // An incremental search is active, we restart the search
182         // from the initial point
183         LOG( logDEBUG ) << "Restart search from initial point";
184         start_position = incrementalSearchStatus_.position();
185     }
186     else {
187         // It's a new search so we search from the selection
188         incrementalSearchStatus_ = IncrementalSearchStatus(
189                 Backward,
190                 start_position,
191                 *selection_ );
192     }
193 
194     qint64 line_found = doSearchBackward( start_position );
195 
196     if ( line_found >= 0 ) {
197         // We have found a result...
198         // ... the caller will jump to this line.
199         return line_found;
200     }
201     else {
202         // No result...
203         // ... we want the client to show the initial line.
204         selection_->clear();
205         return incrementalSearchStatus_.position().line();
206     }
207 }
208 
searchForward()209 qint64 QuickFind::searchForward()
210 {
211     incrementalSearchStatus_ = IncrementalSearchStatus();
212 
213     // Position where we start the search from
214     FilePosition start_position = selection_->getNextPosition();
215 
216     return doSearchForward( start_position );
217 }
218 
219 
searchBackward()220 qint64 QuickFind::searchBackward()
221 {
222     incrementalSearchStatus_ = IncrementalSearchStatus();
223 
224     // Position where we start the search from
225     FilePosition start_position = selection_->getPreviousPosition();
226 
227     return doSearchBackward( start_position );
228 }
229 
230 // Internal implementation of forward search,
231 // returns the line where the pattern is found or -1 if not found.
232 // Parameters are the position the search shall start
doSearchForward(const FilePosition & start_position)233 qint64 QuickFind::doSearchForward( const FilePosition &start_position )
234 {
235     bool found = false;
236     int found_start_col;
237     int found_end_col;
238 
239     if ( searchState_ == SearchState::OnGoing ) {
240         // This happens if this function is re-entered via the notifier
241         // We want the top most call to restart the search
242         searchState_ = SearchState::RestartNeeded;
243         return -1;
244     }
245 
246     if ( ! quickFindPattern_->isActive() )
247         return -1;
248 
249     // Optimisation: if we are already after the last match,
250     // we don't do any search at all.
251     if ( lastMatch_.isLater( start_position ) ) {
252         // Send a notification
253         emit notify( QFNotificationReachedEndOfFile() );
254 
255         return -1;
256     }
257 
258     qint64 line = start_position.line();
259     LOG( logDEBUG ) << "Start searching at line " << line;
260     // We look at the rest of the first line
261     if ( quickFindPattern_->isLineMatching(
262                 logData_->getExpandedLineString( line ),
263                 start_position.column() ) ) {
264         quickFindPattern_->getLastMatch( &found_start_col, &found_end_col );
265         found = true;
266     }
267     else {
268         searchingNotifier_.reset();
269         searchState_ = SearchState::OnGoing;
270         // And then the rest of the file
271         qint64 nb_lines = logData_->getNbLine();
272         int start_line = ++line;
273         while ( line < nb_lines ) {
274             // Check if someone has changed the search within this loop
275             if ( searchState_ == SearchState::RestartNeeded ) {
276                 line = start_line;
277                 searchState_ = SearchState::OnGoing;
278                 LOG( logDEBUG ) << "QuickFind restarted at line " << line;
279             }
280 
281             if ( quickFindPattern_->isLineMatching(
282                         logData_->getExpandedLineString( line ) ) ) {
283                 quickFindPattern_->getLastMatch(
284                         &found_start_col, &found_end_col );
285                 found = true;
286                 LOG( logDEBUG ) << "QuickFind found!";
287                 break;
288             }
289             line++;
290 
291             // See if we need to notify of the ongoing search
292             // Note this could re-enter this function if the pattern is
293             // modified whilst the search is on-going, this is okay.
294             searchingNotifier_.ping( line, nb_lines );
295         }
296 
297         searchState_ = SearchState::Idle;
298     }
299 
300     if ( found ) {
301         selection_->selectPortion(
302                 line, found_start_col, found_end_col );
303 
304         // Clear any notification
305         emit clearNotification();
306 
307         return line;
308     }
309     else {
310         // Update the position of the last match
311         FilePosition last_match_position = selection_->getPreviousPosition();
312         lastMatch_.set( last_match_position );
313 
314         // Send a notification
315         emit notify( QFNotificationReachedEndOfFile() );
316 
317         return -1;
318     }
319 }
320 
321 // Internal implementation of backward search,
322 // returns the line where the pattern is found or -1 if not found.
323 // Parameters are the position the search shall start
doSearchBackward(const FilePosition & start_position)324 qint64 QuickFind::doSearchBackward( const FilePosition &start_position )
325 {
326     bool found = false;
327     int start_col;
328     int end_col;
329 
330     if ( ! quickFindPattern_->isActive() )
331         return -1;
332 
333     // Optimisation: if we are already before the first match,
334     // we don't do any search at all.
335     if ( firstMatch_.isSooner( start_position ) ) {
336         // Send a notification
337         emit notify( QFNotificationReachedBegininningOfFile() );
338 
339         return -1;
340     }
341 
342     qint64 line = start_position.line();
343     LOG( logDEBUG ) << "Start searching at line " << line;
344     // We look at the beginning of the first line
345     if (    ( start_position.column() > 0 )
346          && ( quickFindPattern_->isLineMatchingBackward(
347                  logData_->getExpandedLineString( line ),
348                  start_position.column() ) )
349        ) {
350         quickFindPattern_->getLastMatch( &start_col, &end_col );
351         found = true;
352     }
353     else {
354         searchingNotifier_.reset();
355         // And then the rest of the file
356         qint64 nb_lines = logData_->getNbLine();
357         line--;
358         while ( line >= 0 ) {
359             if ( quickFindPattern_->isLineMatchingBackward(
360                         logData_->getExpandedLineString( line ) ) ) {
361                 quickFindPattern_->getLastMatch( &start_col, &end_col );
362                 found = true;
363                 break;
364             }
365             line--;
366 
367             // See if we need to notify of the ongoing search
368             searchingNotifier_.ping( -line, nb_lines );
369         }
370     }
371 
372     if ( found )
373     {
374         selection_->selectPortion( line, start_col, end_col );
375 
376         // Clear any notification
377         emit clearNotification();
378 
379         return line;
380     }
381     else {
382         // Update the position of the first match
383         FilePosition first_match_position = selection_->getNextPosition();
384         firstMatch_.set( first_match_position );
385 
386         // Send a notification
387         LOG( logDEBUG ) << "QF: Send BOF notification.";
388         emit notify( QFNotificationReachedBegininningOfFile() );
389 
390         return -1;
391     }
392 }
393 
resetLimits()394 void QuickFind::resetLimits()
395 {
396     lastMatch_.reset();
397     firstMatch_.reset();
398 }
399