mirror of
https://github.com/walles/moar.git
synced 2024-09-11 12:15:43 +03:00
Compare commits
13 Commits
edb3e51680
...
f434895eef
Author | SHA1 | Date | |
---|---|---|---|
|
f434895eef | ||
|
7b032b2fa4 | ||
|
686882ffcd | ||
|
60d3577b59 | ||
|
64ae443e1f | ||
|
856574c289 | ||
|
ee35c6b166 | ||
|
625193933d | ||
|
387fb348c7 | ||
|
84cf4a9cd9 | ||
|
aa342d4325 | ||
|
27d03630a5 | ||
|
0ba0415a50 |
114
m/search.go
114
m/search.go
@ -2,6 +2,9 @@ package m
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/walles/moar/m/linenumbers"
|
"github.com/walles/moar/m/linenumbers"
|
||||||
)
|
)
|
||||||
@ -12,10 +15,16 @@ func (p *Pager) scrollToSearchHits() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
firstHitPosition := p.findFirstHit(*p.scrollPosition.lineNumber(p), nil, false)
|
lineNumber := p.scrollPosition.lineNumber(p)
|
||||||
|
if lineNumber == nil {
|
||||||
|
// No lines to search
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
firstHitPosition := p.findFirstHit(*lineNumber, nil, false)
|
||||||
if firstHitPosition == nil {
|
if firstHitPosition == nil {
|
||||||
// Try again from the top
|
// Try again from the top
|
||||||
firstHitPosition = p.findFirstHit(linenumbers.LineNumber{}, p.scrollPosition.lineNumber(p), false)
|
firstHitPosition = p.findFirstHit(linenumbers.LineNumber{}, lineNumber, false)
|
||||||
}
|
}
|
||||||
if firstHitPosition == nil {
|
if firstHitPosition == nil {
|
||||||
// No match, give up
|
// No match, give up
|
||||||
@ -30,12 +39,97 @@ func (p *Pager) scrollToSearchHits() {
|
|||||||
p.scrollPosition = *firstHitPosition
|
p.scrollPosition = *firstHitPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: When we search, we do that by looping over the *input lines*, not
|
// NOTE: When we search, we do that by looping over the *input lines*, not the
|
||||||
// the screen lines. That's why we're using a line number rather than a
|
// screen lines. That's why startPosition is a LineNumber rather than a
|
||||||
// scrollPosition for searching.
|
// scrollPosition.
|
||||||
|
//
|
||||||
|
// The `beforePosition` parameter is exclusive, meaning that line will not be
|
||||||
|
// searched.
|
||||||
|
//
|
||||||
|
// For the actual searching, this method will call _findFirstHit() in parallel
|
||||||
|
// on multiple cores, to help large file search performance.
|
||||||
//
|
//
|
||||||
// FIXME: We should take startPosition.deltaScreenLines into account as well!
|
// FIXME: We should take startPosition.deltaScreenLines into account as well!
|
||||||
func (p *Pager) findFirstHit(startPosition linenumbers.LineNumber, beforePosition *linenumbers.LineNumber, backwards bool) *scrollPosition {
|
func (p *Pager) findFirstHit(startPosition linenumbers.LineNumber, beforePosition *linenumbers.LineNumber, backwards bool) *scrollPosition {
|
||||||
|
// If the number of lines to search matches the number of cores (or more),
|
||||||
|
// divide the search into chunks. Otherwise use one chunk.
|
||||||
|
chunkCount := runtime.NumCPU()
|
||||||
|
var linesCount int
|
||||||
|
if backwards {
|
||||||
|
// If the startPosition is zero, that should make the count one
|
||||||
|
linesCount = startPosition.AsZeroBased() + 1
|
||||||
|
if beforePosition != nil {
|
||||||
|
// Searching from 1 with before set to 0 should make the count 1
|
||||||
|
linesCount = startPosition.AsZeroBased() - beforePosition.AsZeroBased()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
linesCount = p.reader.GetLineCount() - startPosition.AsZeroBased()
|
||||||
|
if beforePosition != nil {
|
||||||
|
// Searching from 1 with before set to 2 should make the count 1
|
||||||
|
linesCount = beforePosition.AsZeroBased() - startPosition.AsZeroBased()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if linesCount < chunkCount {
|
||||||
|
chunkCount = 1
|
||||||
|
}
|
||||||
|
chunkSize := linesCount / chunkCount
|
||||||
|
|
||||||
|
log.Debugf("Searching %d lines across %d cores with %d lines per core", linesCount, chunkCount, chunkSize)
|
||||||
|
|
||||||
|
// Each parallel search will start at one of these positions
|
||||||
|
searchStarts := make([]linenumbers.LineNumber, chunkCount)
|
||||||
|
direction := 1
|
||||||
|
if backwards {
|
||||||
|
direction = -1
|
||||||
|
}
|
||||||
|
for i := 0; i < chunkCount; i++ {
|
||||||
|
searchStarts[i] = startPosition.NonWrappingAdd(i * direction * chunkSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a results array, with one result per chunk
|
||||||
|
findings := make([]chan *scrollPosition, chunkCount)
|
||||||
|
|
||||||
|
// Search all chunks in parallel
|
||||||
|
for i, searchStart := range searchStarts {
|
||||||
|
findings[i] = make(chan *scrollPosition)
|
||||||
|
|
||||||
|
searchEndIndex := i + 1
|
||||||
|
var chunkBefore *linenumbers.LineNumber
|
||||||
|
if searchEndIndex < len(searchStarts) {
|
||||||
|
chunkBefore = &searchStarts[searchEndIndex]
|
||||||
|
} else if beforePosition != nil {
|
||||||
|
chunkBefore = beforePosition
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(i int, searchStart linenumbers.LineNumber, chunkBefore *linenumbers.LineNumber) {
|
||||||
|
findings[i] <- p._findFirstHit(searchStart, chunkBefore, backwards)
|
||||||
|
}(i, searchStart, chunkBefore)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the first non-nil result
|
||||||
|
for _, finding := range findings {
|
||||||
|
result := <-finding
|
||||||
|
if result != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: When we search, we do that by looping over the *input lines*, not the
|
||||||
|
// screen lines. That's why startPosition is a LineNumber rather than a
|
||||||
|
// scrollPosition.
|
||||||
|
//
|
||||||
|
// The `beforePosition` parameter is exclusive, meaning that line will not be
|
||||||
|
// searched.
|
||||||
|
//
|
||||||
|
// This method will run over multiple chunks of the input file in parallel to
|
||||||
|
// help large file search performance.
|
||||||
|
//
|
||||||
|
// FIXME: We should take startPosition.deltaScreenLines into account as well!
|
||||||
|
func (p *Pager) _findFirstHit(startPosition linenumbers.LineNumber, beforePosition *linenumbers.LineNumber, backwards bool) *scrollPosition {
|
||||||
searchPosition := startPosition
|
searchPosition := startPosition
|
||||||
for {
|
for {
|
||||||
line := p.reader.GetLine(searchPosition)
|
line := p.reader.GetLine(searchPosition)
|
||||||
@ -51,18 +145,18 @@ func (p *Pager) findFirstHit(startPosition linenumbers.LineNumber, beforePositio
|
|||||||
|
|
||||||
if backwards {
|
if backwards {
|
||||||
if (searchPosition == linenumbers.LineNumber{}) {
|
if (searchPosition == linenumbers.LineNumber{}) {
|
||||||
// No match, give up
|
// Reached the top without any match, give up
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
searchPosition = searchPosition.NonWrappingAdd(-1)
|
searchPosition = searchPosition.NonWrappingAdd(-1)
|
||||||
} else {
|
} else {
|
||||||
searchPosition = searchPosition.NonWrappingAdd(1)
|
searchPosition = searchPosition.NonWrappingAdd(1)
|
||||||
|
}
|
||||||
|
|
||||||
if beforePosition != nil && searchPosition == *beforePosition {
|
if beforePosition != nil && searchPosition == *beforePosition {
|
||||||
// No match, give up
|
// No match, give up
|
||||||
return nil
|
return nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user