|
Packit |
6978fb |
/*
|
|
Packit |
6978fb |
* Copyright (C) 2015 The Lemon Man
|
|
Packit |
6978fb |
*
|
|
Packit |
6978fb |
* This program is free software; you can redistribute it and/or modify
|
|
Packit |
6978fb |
* it under the terms of the GNU General Public License as published by
|
|
Packit |
6978fb |
* the Free Software Foundation; either version 2, or (at your option)
|
|
Packit |
6978fb |
* any later version.
|
|
Packit |
6978fb |
*
|
|
Packit |
6978fb |
* This program is distributed in the hope that it will be useful,
|
|
Packit |
6978fb |
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
Packit |
6978fb |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
Packit |
6978fb |
* GNU General Public License for more details.
|
|
Packit |
6978fb |
*
|
|
Packit |
6978fb |
* You should have received a copy of the GNU General Public License
|
|
Packit |
6978fb |
* along with this program; if not, write to the Free Software
|
|
Packit |
6978fb |
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
Packit |
6978fb |
*/
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
namespace Gedit {
|
|
Packit |
6978fb |
namespace FindInFilesPlugin {
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
interface IMatcher : Object {
|
|
Packit |
6978fb |
public abstract bool has_match (uint8 *text, size_t text_length, size_t pos, ref Range match);
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
struct Range {
|
|
Packit |
6978fb |
size_t from;
|
|
Packit |
6978fb |
size_t to;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
struct Bookmark {
|
|
Packit |
6978fb |
int line_number;
|
|
Packit |
6978fb |
size_t line_start;
|
|
Packit |
6978fb |
size_t line_length;
|
|
Packit |
6978fb |
public static const Bookmark EmptyBookmark = { 0, 0 };
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
struct Result {
|
|
Packit |
6978fb |
string path;
|
|
Packit |
6978fb |
size_t line;
|
|
Packit |
6978fb |
string context;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
class FindJob {
|
|
Packit |
6978fb |
public signal void on_match_found (Result result);
|
|
Packit |
6978fb |
public signal void on_search_finished ();
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// This queue holds all the file names to scan
|
|
Packit |
6978fb |
private AsyncQueue<string> scan_queue = new AsyncQueue<string>();
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// The list of (hard) workers
|
|
Packit |
6978fb |
private List<Thread<int>> thread_workers;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Count how many workers are still working
|
|
Packit |
6978fb |
private uint running_workers = 0;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
private IMatcher? matcher = null;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
private Cancellable cancellable;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// The actual job parameters
|
|
Packit |
6978fb |
public bool include_hidden;
|
|
Packit |
6978fb |
public bool match_whole_word;
|
|
Packit |
6978fb |
public bool ignore_case;
|
|
Packit |
6978fb |
public string? needle { get; private set; }
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
int worker () {
|
|
Packit |
6978fb |
while (true) {
|
|
Packit |
6978fb |
// Wait 0.5 seconds
|
|
Packit |
6978fb |
var tv = TimeVal ();
|
|
Packit |
6978fb |
tv.add (1000000 / 2);
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
var path = scan_queue.timed_pop (ref tv);
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Check for interruption
|
|
Packit |
6978fb |
if (cancellable.is_cancelled ())
|
|
Packit |
6978fb |
break;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// If path is null then we're probably done
|
|
Packit |
6978fb |
if (path == null)
|
|
Packit |
6978fb |
break;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Scan the file
|
|
Packit |
6978fb |
scan_file (path);
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// We're done, check if we're the last worker active and signal it to the user
|
|
Packit |
6978fb |
lock (running_workers) {
|
|
Packit |
6978fb |
if (0 == (--running_workers)) {
|
|
Packit |
6978fb |
// Run the completion callback in the main thread
|
|
Packit |
6978fb |
Idle.add (() => { on_search_finished (); return false; });
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
return 0;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
public FindJob (Cancellable? cancellable) {
|
|
Packit |
6978fb |
this.cancellable = cancellable ?? new Cancellable ();
|
|
Packit |
6978fb |
needle = null;
|
|
Packit |
6978fb |
include_hidden = false;
|
|
Packit |
6978fb |
match_whole_word = false;
|
|
Packit |
6978fb |
ignore_case = false;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
public void prepare (string needle, bool is_regex) throws Error {
|
|
Packit |
6978fb |
// Construct the matcher instance
|
|
Packit |
6978fb |
if (is_regex) {
|
|
Packit |
6978fb |
matcher = new RegexFind (needle, ignore_case);
|
|
Packit |
6978fb |
} else {
|
|
Packit |
6978fb |
matcher = new BoyerMooreHorspool (needle, ignore_case);
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
void start_workers_pool () {
|
|
Packit |
6978fb |
if (running_workers != 0) {
|
|
Packit |
6978fb |
return;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
var online_cpus = get_num_processors ();
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
for (var i = 0; i < online_cpus; i++) {
|
|
Packit |
6978fb |
thread_workers.append (new Thread<int> ("Worker %d".printf (i), worker));
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
running_workers = online_cpus;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
public string extract_context (uint8 *buffer, Range range) {
|
|
Packit |
6978fb |
uint8 *slice = new uint8[range.to - range.from];
|
|
Packit |
6978fb |
Posix.memcpy (slice, buffer + range.from, range.to - range.from);
|
|
Packit |
6978fb |
return Gedit.utils_make_valid_utf8 ((string)slice);
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
public void halt () {
|
|
Packit |
6978fb |
if (running_workers == 0)
|
|
Packit |
6978fb |
return;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
cancellable.cancel ();
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
foreach (var worker in thread_workers) {
|
|
Packit |
6978fb |
worker.join ();
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
bool is_binary (uint8 *buffer, size_t buffer_size) {
|
|
Packit |
6978fb |
// Poor-man binary detection, check for NULL bytes
|
|
Packit |
6978fb |
return Posix.memchr (buffer, '\0', buffer_size) != null;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
bool ignore_vcs_dir (FileInfo file) {
|
|
Packit |
6978fb |
if (file.get_file_type () == FileType.DIRECTORY) {
|
|
Packit |
6978fb |
var name = file.get_name ();
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
if (name == ".git" ||
|
|
Packit |
6978fb |
name == ".bzr" ||
|
|
Packit |
6978fb |
name == ".svn" ||
|
|
Packit |
6978fb |
name == ".hg" ||
|
|
Packit |
6978fb |
name == "_darcs")
|
|
Packit |
6978fb |
return true;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
return false;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
public async void execute (string root) throws Error {
|
|
Packit |
6978fb |
var queue = new Queue<File> ();
|
|
Packit |
6978fb |
var visited = new HashTable<string, bool> (str_hash, str_equal);
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
start_workers_pool ();
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
queue.push_tail (File.new_for_path (root));
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
while (!queue.is_empty ()) {
|
|
Packit |
6978fb |
if (cancellable.is_cancelled ())
|
|
Packit |
6978fb |
break;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
var dir = queue.pop_head ();
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
var e = yield dir.enumerate_children_async (FileAttribute.STANDARD_NAME,
|
|
Packit |
6978fb |
FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
|
|
Packit |
6978fb |
Priority.DEFAULT);
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Process the entries we got so far
|
|
Packit |
6978fb |
while (true) {
|
|
Packit |
6978fb |
var files = yield e.next_files_async (100);
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
if (files == null)
|
|
Packit |
6978fb |
break;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
if (cancellable.is_cancelled ())
|
|
Packit |
6978fb |
break;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
foreach (var file in files) {
|
|
Packit |
6978fb |
if (!include_hidden && file.get_name ()[0] == '.')
|
|
Packit |
6978fb |
continue;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
if (ignore_vcs_dir (file))
|
|
Packit |
6978fb |
continue;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
var _file = dir.resolve_relative_path (file.get_name ());
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
switch (file.get_file_type ()) {
|
|
Packit |
6978fb |
case FileType.REGULAR:
|
|
Packit |
6978fb |
scan_queue.push (_file.get_path ());
|
|
Packit |
6978fb |
break;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
case FileType.DIRECTORY:
|
|
Packit |
6978fb |
queue.push_tail (_file);
|
|
Packit |
6978fb |
visited.insert (_file.get_path (), true);
|
|
Packit |
6978fb |
break;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
case FileType.SYMBOLIC_LINK:
|
|
Packit |
6978fb |
var link_dest = Posix.realpath (_file.get_path ());
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Follow the link only if its not broken and we didn't visit it
|
|
Packit |
6978fb |
if (link_dest != null && !visited.contains (link_dest)) {
|
|
Packit |
6978fb |
if (FileUtils.test (link_dest, FileTest.IS_DIR) &&
|
|
Packit |
6978fb |
(!ignore_case && link_dest[0] != '.'))
|
|
Packit |
6978fb |
queue.push_tail (File.new_for_path (link_dest));
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
break;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Returns the line number where the text span starting at 'from' and ending at 'to' lies.
|
|
Packit |
6978fb |
void get_line (uint8 *buffer, size_t buffer_size, ref Range span, ref Bookmark bookmark) {
|
|
Packit |
6978fb |
// We take an advantage by knowing that all the calls to get_line are sequential, hence
|
|
Packit |
6978fb |
// we save the position of the last matched line and start from there
|
|
Packit |
6978fb |
var line_count = bookmark.line_number;
|
|
Packit |
6978fb |
var line_start = bookmark.line_start;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
var ptr = buffer + bookmark.line_start;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
while (ptr < buffer + buffer_size) {
|
|
Packit |
6978fb |
// Find the newline
|
|
Packit |
6978fb |
uint8 *nl = Posix.memchr (ptr, '\n', buffer_size - (ptr - buffer));
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
if (nl == null) {
|
|
Packit |
6978fb |
// No more newlines, consider the trailing NULL as the final newline
|
|
Packit |
6978fb |
nl = buffer + buffer_size + 1;
|
|
Packit |
6978fb |
} else {
|
|
Packit |
6978fb |
// Skip the '\n'
|
|
Packit |
6978fb |
nl++;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
var line_length = nl - ptr;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Check if the match is within this line
|
|
Packit |
6978fb |
if (span.from >= line_start && span.to < line_start + line_length) {
|
|
Packit |
6978fb |
// Update the bookmark
|
|
Packit |
6978fb |
bookmark.line_number = line_count;
|
|
Packit |
6978fb |
bookmark.line_start = line_start;
|
|
Packit |
6978fb |
bookmark.line_length = line_length - 1;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
return;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
line_count++;
|
|
Packit |
6978fb |
line_start += line_length;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
ptr = nl;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// There's no way to exit the loop, unless there's a problem in the logic above
|
|
Packit |
6978fb |
assert_not_reached ();
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
bool is_word_boundary (uint8 *buf, size_t buf_size, size_t from, size_t to) {
|
|
Packit |
6978fb |
unichar ch;
|
|
Packit |
6978fb |
bool prev, next;
|
|
Packit |
6978fb |
unowned string str;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
assert (to > from && to <= buf_size);
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// There's not much we can do
|
|
Packit |
6978fb |
if (to - from > int.MAX)
|
|
Packit |
6978fb |
return false;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Silence the warning about ch being uninitialized
|
|
Packit |
6978fb |
ch = '\0';
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
prev = next = true;
|
|
Packit |
6978fb |
str = (string)(buf + from);
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Those are being modified by the get_{prev,next}_char calls
|
|
Packit |
6978fb |
int start = 0;
|
|
Packit |
6978fb |
int end = (int)(to - from);
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// The match is on a word boundary if there are no consecutive alphanumeric
|
|
Packit |
6978fb |
// characters right before or after the match
|
|
Packit |
6978fb |
var head = str.get_char (0);
|
|
Packit |
6978fb |
if (start > 0 && str.get_prev_char (ref start, out ch))
|
|
Packit |
6978fb |
prev = head.isalnum () != ch.isalnum ();
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
var tail = str.get_char (end - 1);
|
|
Packit |
6978fb |
if (end < buf_size && str.get_next_char (ref end, out ch))
|
|
Packit |
6978fb |
next = tail.isalnum () != ch.isalnum ();
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
return prev && next;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
void scan_file (string path) {
|
|
Packit |
6978fb |
MappedFile file;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
try {
|
|
Packit |
6978fb |
file = new MappedFile (path, false);
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
catch (FileError err) {
|
|
Packit |
6978fb |
warning (err.message);
|
|
Packit |
6978fb |
return;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
var buffer_size = file.get_length ();
|
|
Packit |
6978fb |
var buffer = (uint8 *)file.get_contents ();
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Skip binary files for obvious reasons
|
|
Packit |
6978fb |
if (is_binary (buffer, buffer_size))
|
|
Packit |
6978fb |
return;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
Range match = { 0, 0 };
|
|
Packit |
6978fb |
Bookmark bookmark = Bookmark.EmptyBookmark;
|
|
Packit |
6978fb |
var last_line = -1;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
for (size_t buffer_pos = 0; buffer_pos < buffer_size; ) {
|
|
Packit |
6978fb |
if (cancellable.is_cancelled ())
|
|
Packit |
6978fb |
break;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Exit when there's no match
|
|
Packit |
6978fb |
if (!matcher.has_match (buffer, buffer_size, buffer_pos, ref match))
|
|
Packit |
6978fb |
break;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Check if the match lies on a word boundary
|
|
Packit |
6978fb |
if (match_whole_word) {
|
|
Packit |
6978fb |
if (!is_word_boundary (buffer, buffer_size, (int)match.from, (int)match.to)) {
|
|
Packit |
6978fb |
buffer_pos = match.to;
|
|
Packit |
6978fb |
continue;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
get_line (buffer, buffer_size, ref match, ref bookmark);
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Find out what line the match lies in
|
|
Packit |
6978fb |
var match_line = 1 + bookmark.line_number;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Notify that we got a match
|
|
Packit |
6978fb |
if (last_line != match_line) {
|
|
Packit |
6978fb |
Result res = { path, match_line, extract_context (buffer, match) };
|
|
Packit |
6978fb |
on_match_found (res);
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
last_line = match_line;
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
// Keep searching past the match
|
|
Packit |
6978fb |
buffer_pos = match.to;
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
}
|
|
Packit |
6978fb |
|
|
Packit |
6978fb |
} // namespace FindInFilesPlugin
|
|
Packit |
6978fb |
} // namespace Gedit
|