예제 #1
0
// NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
	input := []rune(opts.Query)
	var header []string
	if opts.Reverse {
		header = opts.Header
	} else {
		header = reverseStringArray(opts.Header)
	}
	_tabStop = opts.Tabstop
	var delay time.Duration
	if opts.Tac {
		delay = initialDelayTac
	} else {
		delay = initialDelay
	}
	var previewBox *util.EventBox
	if len(opts.Preview.command) > 0 {
		previewBox = util.NewEventBox()
	}
	strongAttr := tui.Bold
	if !opts.Bold {
		strongAttr = tui.AttrRegular
	}
	return &Terminal{
		initDelay:  delay,
		inlineInfo: opts.InlineInfo,
		prompt:     opts.Prompt,
		reverse:    opts.Reverse,
		hscroll:    opts.Hscroll,
		hscrollOff: opts.HscrollOff,
		cx:         len(input),
		cy:         0,
		offset:     0,
		yanked:     []rune{},
		input:      input,
		multi:      opts.Multi,
		sort:       opts.Sort > 0,
		toggleSort: opts.ToggleSort,
		delimiter:  opts.Delimiter,
		expect:     opts.Expect,
		keymap:     opts.Keymap,
		execmap:    opts.Execmap,
		pressed:    "",
		printQuery: opts.PrintQuery,
		history:    opts.History,
		margin:     opts.Margin,
		strong:     strongAttr,
		cycle:      opts.Cycle,
		header:     header,
		header0:    header,
		ansi:       opts.Ansi,
		reading:    true,
		jumping:    jumpDisabled,
		jumpLabels: opts.JumpLabels,
		printer:    opts.Printer,
		merger:     EmptyMerger,
		selected:   make(map[int32]selectedItem),
		reqBox:     util.NewEventBox(),
		preview:    opts.Preview,
		previewer:  previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden},
		previewBox: previewBox,
		eventBox:   eventBox,
		mutex:      sync.Mutex{},
		suppress:   true,
		slab:       util.MakeSlab(slab16Size, slab32Size),
		theme:      opts.Theme,
		startChan:  make(chan bool, 1),
		initFunc: func() {
			tui.Init(opts.Theme, opts.Black, opts.Mouse)
		}}
}
예제 #2
0
func (m *Matcher) scan(request MatchRequest) (*Merger, bool) {
	startedAt := time.Now()

	numChunks := len(request.chunks)
	if numChunks == 0 {
		return EmptyMerger, false
	}
	pattern := request.pattern
	if pattern.IsEmpty() {
		return PassMerger(&request.chunks, m.tac), false
	}

	cancelled := util.NewAtomicBool(false)

	slices := m.sliceChunks(request.chunks)
	numSlices := len(slices)
	resultChan := make(chan partialResult, numSlices)
	countChan := make(chan int, numChunks)
	waitGroup := sync.WaitGroup{}

	for idx, chunks := range slices {
		waitGroup.Add(1)
		if m.slab[idx] == nil {
			m.slab[idx] = util.MakeSlab(slab16Size, slab32Size)
		}
		go func(idx int, slab *util.Slab, chunks []*Chunk) {
			defer func() { waitGroup.Done() }()
			count := 0
			allMatches := make([][]*Result, len(chunks))
			for idx, chunk := range chunks {
				matches := request.pattern.Match(chunk, slab)
				allMatches[idx] = matches
				count += len(matches)
				if cancelled.Get() {
					return
				}
				countChan <- len(matches)
			}
			sliceMatches := make([]*Result, 0, count)
			for _, matches := range allMatches {
				sliceMatches = append(sliceMatches, matches...)
			}
			if m.sort {
				if m.tac {
					sort.Sort(ByRelevanceTac(sliceMatches))
				} else {
					sort.Sort(ByRelevance(sliceMatches))
				}
			}
			resultChan <- partialResult{idx, sliceMatches}
		}(idx, m.slab[idx], chunks)
	}

	wait := func() bool {
		cancelled.Set(true)
		waitGroup.Wait()
		return true
	}

	count := 0
	matchCount := 0
	for matchesInChunk := range countChan {
		count++
		matchCount += matchesInChunk

		if count == numChunks {
			break
		}

		if m.reqBox.Peek(reqReset) {
			return nil, wait()
		}

		if time.Now().Sub(startedAt) > progressMinDuration {
			m.eventBox.Set(EvtSearchProgress, float32(count)/float32(numChunks))
		}
	}

	partialResults := make([][]*Result, numSlices)
	for _ = range slices {
		partialResult := <-resultChan
		partialResults[partialResult.index] = partialResult.matches
	}
	return NewMerger(pattern, partialResults, m.sort, m.tac), false
}
예제 #3
0
func init() {
	slab = util.MakeSlab(slab16Size, slab32Size)
}
예제 #4
0
// Run starts fzf
func Run(opts *Options) {
	sort := opts.Sort > 0
	sortCriteria = opts.Criteria

	if opts.Version {
		fmt.Println(version)
		os.Exit(exitOk)
	}

	// Event channel
	eventBox := util.NewEventBox()

	// ANSI code processor
	ansiProcessor := func(data []byte) (util.Chars, *[]ansiOffset) {
		return util.ToChars(data), nil
	}
	ansiProcessorRunes := func(data []rune) (util.Chars, *[]ansiOffset) {
		return util.RunesToChars(data), nil
	}
	if opts.Ansi {
		if opts.Theme != nil {
			var state *ansiState
			ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
				trimmed, offsets, newState := extractColor(string(data), state, nil)
				state = newState
				return util.RunesToChars([]rune(trimmed)), offsets
			}
		} else {
			// When color is disabled but ansi option is given,
			// we simply strip out ANSI codes from the input
			ansiProcessor = func(data []byte) (util.Chars, *[]ansiOffset) {
				trimmed, _, _ := extractColor(string(data), nil, nil)
				return util.RunesToChars([]rune(trimmed)), nil
			}
		}
		ansiProcessorRunes = func(data []rune) (util.Chars, *[]ansiOffset) {
			return ansiProcessor([]byte(string(data)))
		}
	}

	// Chunk list
	var chunkList *ChunkList
	header := make([]string, 0, opts.HeaderLines)
	if len(opts.WithNth) == 0 {
		chunkList = NewChunkList(func(data []byte, index int) *Item {
			if len(header) < opts.HeaderLines {
				header = append(header, string(data))
				eventBox.Set(EvtHeader, header)
				return nil
			}
			chars, colors := ansiProcessor(data)
			return &Item{
				index:  int32(index),
				text:   chars,
				colors: colors}
		})
	} else {
		chunkList = NewChunkList(func(data []byte, index int) *Item {
			tokens := Tokenize(util.ToChars(data), opts.Delimiter)
			trans := Transform(tokens, opts.WithNth)
			if len(header) < opts.HeaderLines {
				header = append(header, string(joinTokens(trans)))
				eventBox.Set(EvtHeader, header)
				return nil
			}
			textRunes := joinTokens(trans)
			item := Item{
				index:    int32(index),
				origText: &data,
				colors:   nil}

			trimmed, colors := ansiProcessorRunes(textRunes)
			item.text = trimmed
			item.colors = colors
			return &item
		})
	}

	// Reader
	streamingFilter := opts.Filter != nil && !sort && !opts.Tac && !opts.Sync
	if !streamingFilter {
		reader := Reader{func(data []byte) bool {
			return chunkList.Push(data)
		}, eventBox, opts.ReadZero}
		go reader.ReadSource()
	}

	// Matcher
	forward := true
	for _, cri := range opts.Criteria[1:] {
		if cri == byEnd {
			forward = false
			break
		}
		if cri == byBegin {
			break
		}
	}
	patternBuilder := func(runes []rune) *Pattern {
		return BuildPattern(
			opts.Fuzzy, opts.FuzzyAlgo, opts.Extended, opts.Case, forward,
			opts.Filter == nil, opts.Nth, opts.Delimiter, runes)
	}
	matcher := NewMatcher(patternBuilder, sort, opts.Tac, eventBox)

	// Filtering mode
	if opts.Filter != nil {
		if opts.PrintQuery {
			opts.Printer(*opts.Filter)
		}

		pattern := patternBuilder([]rune(*opts.Filter))

		found := false
		if streamingFilter {
			slab := util.MakeSlab(slab16Size, slab32Size)
			reader := Reader{
				func(runes []byte) bool {
					item := chunkList.trans(runes, 0)
					if item != nil {
						if result, _, _ := pattern.MatchItem(item, false, slab); result != nil {
							opts.Printer(item.text.ToString())
							found = true
						}
					}
					return false
				}, eventBox, opts.ReadZero}
			reader.ReadSource()
		} else {
			eventBox.Unwatch(EvtReadNew)
			eventBox.WaitFor(EvtReadFin)

			snapshot, _ := chunkList.Snapshot()
			merger, _ := matcher.scan(MatchRequest{
				chunks:  snapshot,
				pattern: pattern})
			for i := 0; i < merger.Length(); i++ {
				opts.Printer(merger.Get(i).item.AsString(opts.Ansi))
				found = true
			}
		}
		if found {
			os.Exit(exitOk)
		}
		os.Exit(exitNoMatch)
	}

	// Synchronous search
	if opts.Sync {
		eventBox.Unwatch(EvtReadNew)
		eventBox.WaitFor(EvtReadFin)
	}

	// Go interactive
	go matcher.Loop()

	// Terminal I/O
	terminal := NewTerminal(opts, eventBox)
	deferred := opts.Select1 || opts.Exit0
	go terminal.Loop()
	if !deferred {
		terminal.startChan <- true
	}

	// Event coordination
	reading := true
	ticks := 0
	eventBox.Watch(EvtReadNew)
	for {
		delay := true
		ticks++
		eventBox.Wait(func(events *util.Events) {
			defer events.Clear()
			for evt, value := range *events {
				switch evt {

				case EvtReadNew, EvtReadFin:
					reading = reading && evt == EvtReadNew
					snapshot, count := chunkList.Snapshot()
					terminal.UpdateCount(count, !reading)
					matcher.Reset(snapshot, terminal.Input(), false, !reading, sort)

				case EvtSearchNew:
					switch val := value.(type) {
					case bool:
						sort = val
					}
					snapshot, _ := chunkList.Snapshot()
					matcher.Reset(snapshot, terminal.Input(), true, !reading, sort)
					delay = false

				case EvtSearchProgress:
					switch val := value.(type) {
					case float32:
						terminal.UpdateProgress(val)
					}

				case EvtHeader:
					terminal.UpdateHeader(value.([]string))

				case EvtSearchFin:
					switch val := value.(type) {
					case *Merger:
						if deferred {
							count := val.Length()
							if opts.Select1 && count > 1 || opts.Exit0 && !opts.Select1 && count > 0 {
								deferred = false
								terminal.startChan <- true
							} else if val.final {
								if opts.Exit0 && count == 0 || opts.Select1 && count == 1 {
									if opts.PrintQuery {
										opts.Printer(opts.Query)
									}
									if len(opts.Expect) > 0 {
										opts.Printer("")
									}
									for i := 0; i < count; i++ {
										opts.Printer(val.Get(i).item.AsString(opts.Ansi))
									}
									if count > 0 {
										os.Exit(exitOk)
									}
									os.Exit(exitNoMatch)
								}
								deferred = false
								terminal.startChan <- true
							}
						}
						terminal.UpdateList(val)
					}
				}
			}
		})
		if delay && reading {
			dur := util.DurWithin(
				time.Duration(ticks)*coordinatorDelayStep,
				0, coordinatorDelayMax)
			time.Sleep(dur)
		}
	}
}
예제 #5
0
파일: terminal.go 프로젝트: junegunn/fzf
// NewTerminal returns new Terminal object
func NewTerminal(opts *Options, eventBox *util.EventBox) *Terminal {
	input := []rune(opts.Query)
	var header []string
	if opts.Reverse {
		header = opts.Header
	} else {
		header = reverseStringArray(opts.Header)
	}
	var delay time.Duration
	if opts.Tac {
		delay = initialDelayTac
	} else {
		delay = initialDelay
	}
	var previewBox *util.EventBox
	if len(opts.Preview.command) > 0 {
		previewBox = util.NewEventBox()
	}
	strongAttr := tui.Bold
	if !opts.Bold {
		strongAttr = tui.AttrRegular
	}
	var renderer tui.Renderer
	if opts.Height.size > 0 {
		maxHeightFunc := func(termHeight int) int {
			var maxHeight int
			if opts.Height.percent {
				maxHeight = util.Min(termHeight,
					util.Max(int(opts.Height.size*float64(termHeight)/100.0), opts.MinHeight))
			} else {
				maxHeight = util.Min(termHeight, int(opts.Height.size))
			}
			if opts.InlineInfo {
				return util.Max(maxHeight, minHeight-1)
			}
			return util.Max(maxHeight, minHeight)
		}
		renderer = tui.NewLightRenderer(opts.Theme, opts.Black, opts.Mouse, opts.Tabstop, maxHeightFunc)
	} else {
		renderer = tui.NewFullscreenRenderer(opts.Theme, opts.Black, opts.Mouse)
	}
	wordRubout := "[^[:alnum:]][[:alnum:]]"
	wordNext := "[[:alnum:]][^[:alnum:]]|(.$)"
	if opts.FileWord {
		sep := regexp.QuoteMeta(string(os.PathSeparator))
		wordRubout = fmt.Sprintf("%s[^%s]", sep, sep)
		wordNext = fmt.Sprintf("[^%s]%s|(.$)", sep, sep)
	}
	return &Terminal{
		initDelay:  delay,
		inlineInfo: opts.InlineInfo,
		prompt:     opts.Prompt,
		reverse:    opts.Reverse,
		hscroll:    opts.Hscroll,
		hscrollOff: opts.HscrollOff,
		wordRubout: wordRubout,
		wordNext:   wordNext,
		cx:         len(input),
		cy:         0,
		offset:     0,
		yanked:     []rune{},
		input:      input,
		multi:      opts.Multi,
		sort:       opts.Sort > 0,
		toggleSort: opts.ToggleSort,
		delimiter:  opts.Delimiter,
		expect:     opts.Expect,
		keymap:     opts.Keymap,
		execmap:    opts.Execmap,
		pressed:    "",
		printQuery: opts.PrintQuery,
		history:    opts.History,
		margin:     opts.Margin,
		strong:     strongAttr,
		cycle:      opts.Cycle,
		header:     header,
		header0:    header,
		ansi:       opts.Ansi,
		tabstop:    opts.Tabstop,
		reading:    true,
		jumping:    jumpDisabled,
		jumpLabels: opts.JumpLabels,
		printer:    opts.Printer,
		merger:     EmptyMerger,
		selected:   make(map[int32]selectedItem),
		reqBox:     util.NewEventBox(),
		preview:    opts.Preview,
		previewer:  previewer{"", 0, 0, previewBox != nil && !opts.Preview.hidden},
		previewBox: previewBox,
		eventBox:   eventBox,
		mutex:      sync.Mutex{},
		suppress:   true,
		slab:       util.MakeSlab(slab16Size, slab32Size),
		theme:      opts.Theme,
		startChan:  make(chan bool, 1),
		tui:        renderer,
		initFunc:   func() { renderer.Init() }}
}