std/search

Searching a project's files, as an editor's search does.

import "std/search"

SearchOptions o = SearchOptions("todo")
o.ignore_case = true
o.include = ["*.cz"]
search_files("src", o, func(SearchMatch m) bool do
    print(f"{m.path}:{m.line}:{m.column}: {m.text}")
    return true
end)?

The files are those under the folder but the ones that .gitignore, .ignore, and .git/info/exclude leave out, as Git reads them, .git itself, hidden ones (whose names start with .), binary ones (with a zero byte in their first 8,000), and those over max_file_size.

SearchOptions
query the text, or a regular expression of std/regex when regex is true
ignore_case, whole_word
multiline a regular expression may match across lines; text with \n always may
context how many lines before and after each match come with it
include, exclude patterns, as in .gitignore, of the files to search and of files and folders to leave out
use_ignore_files, hidden, max_file_size which files are searched
max_matches the search stops after this many
Function Result
search_files(root, options, found) gives found each SearchMatch as it is found, until it returns false, and gives a SearchSummary: how many files were read, how many matches found, and whether it stopped
search(root, options) every match
project_files(root, options) the files a search reads, as paths from root with /
replace_preview(root, options, replacement) the FileReplacements that replacing every match would make: each file's path, its text before and after, and how many matches it had (count); a regular expression's replacement takes $1, ${1}, ${name}, and $$
apply_replacements(root, replacements) writes them, or none when a file changed since the preview, which is an ERROR of kind "changed"
file_index(root) a FileIndex of the files' names: index.find(query) gives the fuzzy matches, the best first; index.refresh() reads the folder again
ignore_rules() an IgnoreRules: rules.add(folder, text) adds an ignore file's patterns, and rules.ignored(path, is_folder) says whether a path is left out
glob_matches(pattern, path) whether a path fits a glob of .gitignore: *, ?, [a-z], [!a-z], and ** for any folders
is_binary(data) whether bytes look binary, as Git judges

A SearchMatch has the file's path, the match's line and column (in bytes), both from 1, its length in bytes, the text of its lines, and the lines before and after it.