From e14bb29c32b6690404ac0883b319afa233d4fe01 Mon Sep 17 00:00:00 2001 From: Vincent Rischmann Date: Sun, 27 Sep 2020 06:41:47 +0200 Subject: [PATCH] Exclude file path (#347) * index: add a search option to exclude files given a regexp * api: add the excludeFiles query parameter Set the index search options ExcludeFileRegexp with this parameter to allow excluding files from the search via a regexp. * js: add the excludeFiles search parameter * css: increase the label width to fit 'Exclude file path' * js: correctly check that advanced is empty --- api/api.go | 1 + index/index.go | 24 +++++++++++++++---- ui/assets/css/hound.css | 2 +- ui/assets/js/hound.js | 51 ++++++++++++++++++++++++++++++++++------- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/api/api.go b/api/api.go index b0efc3a1..b6340487 100644 --- a/api/api.go +++ b/api/api.go @@ -178,6 +178,7 @@ func Setup(m *http.ServeMux, idx map[string]*searcher.Searcher) { query := r.FormValue("q") opt.Offset, opt.Limit = parseRangeValue(r.FormValue("rng")) opt.FileRegexp = r.FormValue("files") + opt.ExcludeFileRegexp = r.FormValue("excludeFiles") opt.IgnoreCase = parseAsBool(r.FormValue("i")) opt.LinesOfContext = parseAsUintValue( r.FormValue("ctx"), diff --git a/index/index.go b/index/index.go index afb650eb..626ec481 100644 --- a/index/index.go +++ b/index/index.go @@ -41,11 +41,12 @@ type IndexOptions struct { } type SearchOptions struct { - IgnoreCase bool - LinesOfContext uint - FileRegexp string - Offset int - Limit int + IgnoreCase bool + LinesOfContext uint + FileRegexp string + ExcludeFileRegexp string + Offset int + Limit int } type Match struct { @@ -167,6 +168,14 @@ func (n *Index) Search(pat string, opt *SearchOptions) (*SearchResponse, error) } } + var excludeFre *regexp.Regexp + if opt.ExcludeFileRegexp != "" { + excludeFre, err = regexp.Compile(opt.ExcludeFileRegexp) + if err != nil { + return nil, err + } + } + files := n.idx.PostingQuery(index.RegexpQuery(re.Syntax)) for _, file := range files { var matches []*Match @@ -178,6 +187,11 @@ func (n *Index) Search(pat string, opt *SearchOptions) (*SearchResponse, error) continue } + // reject files that match the exclude file pattern + if excludeFre != nil && excludeFre.MatchString(name, true, true) > 0 { + continue + } + filesOpened++ if err := g.grep2File(filepath.Join(n.Ref.dir, "raw", name), re, int(opt.LinesOfContext), func(line []byte, lineno int, before [][]byte, after [][]byte) (bool, error) { diff --git a/ui/assets/css/hound.css b/ui/assets/css/hound.css index c29e3afa..cd29df5c 100644 --- a/ui/assets/css/hound.css +++ b/ui/assets/css/hound.css @@ -126,7 +126,7 @@ button:focus { #adv > .field > label { /* Media object left */ float: left; - width: 90px; + width: 100px; color: #999; } diff --git a/ui/assets/js/hound.js b/ui/assets/js/hound.js index d9be0dad..da991261 100644 --- a/ui/assets/js/hound.js +++ b/ui/assets/js/hound.js @@ -76,6 +76,7 @@ var ParamsFromUrl = function(params) { q: '', i: 'nope', files: '', + excludeFiles: '', repos: '*' }; return ParamsFromQueryString(location.search, params); @@ -360,7 +361,7 @@ var SearchBar = React.createClass({ switch (event.keyCode) { case 38: // if advanced is empty, close it up. - if (this.refs.files.getDOMNode().value.trim() === '') { + if (this.isAdvancedEmpty()) { this.hideAdvanced(); } this.refs.q.getDOMNode().focus(); @@ -373,6 +374,23 @@ var SearchBar = React.createClass({ filesGotFocus: function(event) { this.showAdvanced(); }, + excludeFilesGotKeydown: function(event) { + switch (event.keyCode) { + case 38: + // if advanced is empty, close it up. + if (this.isAdvancedEmpty()) { + this.hideAdvanced(); + } + this.refs.q.getDOMNode().focus(); + break; + case 13: + this.submitQuery(); + break; + } + }, + excludeFilesGotFocus: function(event) { + this.showAdvanced(); + }, submitQuery: function() { this.props.onSearchRequested(this.getParams()); }, @@ -392,6 +410,7 @@ var SearchBar = React.createClass({ return { q : this.refs.q.getDOMNode().value.trim(), files : this.refs.files.getDOMNode().value.trim(), + excludeFiles : this.refs.excludeFiles.getDOMNode().value.trim(), repos : repos.join(','), i: this.refs.icase.getDOMNode().checked ? 'fosho' : 'nope' }; @@ -399,30 +418,32 @@ var SearchBar = React.createClass({ setParams: function(params) { var q = this.refs.q.getDOMNode(), i = this.refs.icase.getDOMNode(), - files = this.refs.files.getDOMNode(); + files = this.refs.files.getDOMNode(), + excludeFiles = this.refs.excludeFiles.getDOMNode(); q.value = params.q; i.checked = ParamValueToBool(params.i); files.value = params.files; + excludeFiles.value = params.excludeFiles; }, hasAdvancedValues: function() { - return this.refs.files.getDOMNode().value.trim() !== '' || this.refs.icase.getDOMNode().checked || this.refs.repos.getDOMNode().value !== ''; + return this.refs.files.getDOMNode().value.trim() !== '' || this.refs.excludeFiles.getDOMNode().value.trim() !== '' || this.refs.icase.getDOMNode().checked || this.refs.repos.getDOMNode().value !== ''; + }, + isAdvancedEmpty: function() { + return this.refs.files.getDOMNode().value.trim() === '' && this.refs.excludeFiles.getDOMNode().value.trim() === ''; }, showAdvanced: function() { var adv = this.refs.adv.getDOMNode(), ban = this.refs.ban.getDOMNode(), q = this.refs.q.getDOMNode(), - files = this.refs.files.getDOMNode(); + files = this.refs.files.getDOMNode(), + excludeFiles = this.refs.excludeFiles.getDOMNode(); css(adv, 'height', 'auto'); css(adv, 'padding', '10px 0'); css(ban, 'max-height', '0'); css(ban, 'opacity', '0'); - - if (q.value.trim() !== '') { - files.focus(); - } }, hideAdvanced: function() { var adv = this.refs.adv.getDOMNode(), @@ -499,6 +520,17 @@ var SearchBar = React.createClass({ onFocus={this.filesGotFocus} /> +
+ +
+ +
+
@@ -763,6 +795,7 @@ var App = React.createClass({ q: params.q, i: params.i, files: params.files, + excludeFiles: params.excludeFiles, repos: repos }); @@ -817,6 +850,7 @@ var App = React.createClass({ '?q=' + encodeURIComponent(params.q) + '&i=' + encodeURIComponent(params.i) + '&files=' + encodeURIComponent(params.files) + + '&excludeFiles=' + encodeURIComponent(params.excludeFiles) + '&repos=' + params.repos; history.pushState({path:path}, '', path); }, @@ -827,6 +861,7 @@ var App = React.createClass({ q={this.state.q} i={this.state.i} files={this.state.files} + excludeFiles={this.state.excludeFiles} repos={this.state.repos} onSearchRequested={this.onSearchRequested} />