github.com/mithrandie/csvq@v1.18.1/lib/terminal/terminal_readline.go (about)

     1  //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
     2  
     3  package terminal
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/mithrandie/csvq/lib/option"
    12  	"github.com/mithrandie/csvq/lib/query"
    13  
    14  	"github.com/mitchellh/go-homedir"
    15  	"github.com/mithrandie/readline-csvq"
    16  )
    17  
    18  type ReadLineTerminal struct {
    19  	terminal  *readline.Instance
    20  	fd        int
    21  	prompt    *Prompt
    22  	env       *option.Environment
    23  	completer *Completer
    24  	tx        *query.Transaction
    25  }
    26  
    27  func NewTerminal(ctx context.Context, scope *query.ReferenceScope) (query.VirtualTerminal, error) {
    28  	fd := int(scope.Tx.Session.ScreenFd())
    29  
    30  	limit := *scope.Tx.Environment.InteractiveShell.HistoryLimit
    31  	historyFile, err := HistoryFilePath(scope.Tx.Environment.InteractiveShell.HistoryFile)
    32  	if err != nil {
    33  		scope.Tx.LogWarn(fmt.Sprintf("cannot detect filepath: %q", scope.Tx.Environment.InteractiveShell.HistoryFile), false)
    34  		limit = -1
    35  	}
    36  
    37  	prompt := NewPrompt(scope)
    38  	completer := NewCompleter(scope)
    39  
    40  	t, err := readline.NewEx(&readline.Config{
    41  		HistoryFile:            historyFile,
    42  		DisableAutoSaveHistory: true,
    43  		HistoryLimit:           limit,
    44  		HistorySearchFold:      true,
    45  		Listener:               new(ReadlineListener),
    46  		Stdin:                  readline.NewCancelableStdin(scope.Tx.Session.Stdin()),
    47  		Stdout:                 scope.Tx.Session.Stdout(),
    48  		Stderr:                 scope.Tx.Session.Stderr(),
    49  	})
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	terminal := ReadLineTerminal{
    55  		terminal:  t,
    56  		fd:        fd,
    57  		prompt:    prompt,
    58  		env:       scope.Tx.Environment,
    59  		completer: completer,
    60  		tx:        scope.Tx,
    61  	}
    62  
    63  	terminal.setCompleter()
    64  	terminal.setKillWholeLine()
    65  	terminal.setViMode()
    66  	if err = prompt.LoadConfig(); err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	terminal.SetPrompt(ctx)
    71  	return terminal, nil
    72  }
    73  
    74  func (t ReadLineTerminal) Teardown() error {
    75  	return t.terminal.Close()
    76  }
    77  
    78  func (t ReadLineTerminal) ReadLine() (string, error) {
    79  	return t.terminal.Readline()
    80  }
    81  
    82  func (t ReadLineTerminal) Write(s string) error {
    83  	_, err := t.terminal.Write([]byte(s))
    84  	return err
    85  }
    86  
    87  func (t ReadLineTerminal) WriteError(s string) error {
    88  	_, err := t.terminal.Stderr().Write([]byte(s))
    89  	return err
    90  }
    91  
    92  func (t ReadLineTerminal) SetPrompt(ctx context.Context) {
    93  	str, err := t.prompt.RenderPrompt(ctx)
    94  	if err != nil {
    95  		t.tx.LogError(err.Error())
    96  	}
    97  	t.terminal.SetPrompt(str)
    98  }
    99  
   100  func (t ReadLineTerminal) SetContinuousPrompt(ctx context.Context) {
   101  	str, err := t.prompt.RenderContinuousPrompt(ctx)
   102  	if err != nil {
   103  		t.tx.LogError(err.Error())
   104  	}
   105  	t.terminal.SetPrompt(str)
   106  }
   107  
   108  func (t ReadLineTerminal) SaveHistory(s string) error {
   109  	return t.terminal.SaveHistory(s)
   110  }
   111  
   112  func (t ReadLineTerminal) GetSize() (int, int, error) {
   113  	return readline.GetSize(t.fd)
   114  }
   115  
   116  func (t ReadLineTerminal) ReloadConfig() error {
   117  	t.setCompleter()
   118  	t.setKillWholeLine()
   119  	t.setViMode()
   120  	return t.prompt.LoadConfig()
   121  }
   122  
   123  func (t ReadLineTerminal) UpdateCompleter() {
   124  	if t.completer != nil {
   125  		t.completer.Update()
   126  	}
   127  }
   128  
   129  func (t ReadLineTerminal) setCompleter() {
   130  	if *t.env.InteractiveShell.Completion {
   131  		t.terminal.Config.AutoComplete = t.completer
   132  	} else {
   133  		t.terminal.Config.AutoComplete = nil
   134  	}
   135  }
   136  
   137  func (t ReadLineTerminal) setKillWholeLine() {
   138  	if *t.env.InteractiveShell.KillWholeLine {
   139  		t.terminal.EnableKillWholeLine()
   140  	} else {
   141  		t.terminal.DisableKillWholeLine()
   142  	}
   143  }
   144  
   145  func (t ReadLineTerminal) setViMode() {
   146  	t.terminal.SetVimMode(*t.env.InteractiveShell.ViMode)
   147  }
   148  
   149  func HistoryFilePath(filename string) (string, error) {
   150  	if filename[0] == '~' {
   151  		if fpath, err := homedir.Expand(filename); err == nil {
   152  			return fpath, nil
   153  		}
   154  	}
   155  
   156  	fpath := os.ExpandEnv(filename)
   157  
   158  	if filepath.IsAbs(fpath) {
   159  		return fpath, nil
   160  	}
   161  
   162  	home, err := homedir.Dir()
   163  	if err != nil {
   164  		return filename, err
   165  	}
   166  	return filepath.Join(home, fpath), nil
   167  }