github.com/jmigpin/editor@v1.6.0/core/lsproto/serverwrap.go (about)

     1  package lsproto
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"strings"
     9  	"text/template"
    10  
    11  	"github.com/jmigpin/editor/util/iout"
    12  	"github.com/jmigpin/editor/util/osutil"
    13  )
    14  
    15  type ServerWrap struct {
    16  	Cmd *osutil.Cmd
    17  	rwc *rwc // just for IO mode (can be nil)
    18  }
    19  
    20  //----------
    21  
    22  func StartServerWrapTCP(ctx context.Context, cmdTmpl string, w io.Writer) (*ServerWrap, string, error) {
    23  	host := "127.0.0.1"
    24  
    25  	// multiple editors can have multiple server wraps, need unique port
    26  	port, err := osutil.GetFreeTcpPort()
    27  	if err != nil {
    28  		return nil, "", err
    29  	}
    30  
    31  	// run cmd template
    32  	cmd, addr, err := cmdTemplate(cmdTmpl, host, port)
    33  	if err != nil {
    34  		return nil, "", err
    35  	}
    36  
    37  	sw := newServerWrapCommon(ctx, cmd)
    38  
    39  	// get lsp server output in tcp mode
    40  	if w != nil {
    41  		if err := sw.Cmd.SetupStdio(nil, w, w); err != nil {
    42  			sw.Cmd.Cancel() // start will not run, clear ctx
    43  			return nil, "", err
    44  		}
    45  	}
    46  
    47  	if err := sw.Cmd.Start(); err != nil {
    48  		return nil, "", err
    49  	}
    50  	return sw, addr, nil
    51  }
    52  
    53  func StartServerWrapIO(ctx context.Context, cmd string, stderr io.Writer, li *LangInstance) (*ServerWrap, io.ReadWriteCloser, error) {
    54  	sw := newServerWrapCommon(ctx, cmd)
    55  
    56  	pr1, pw1 := io.Pipe()
    57  	pr2, pw2 := io.Pipe()
    58  	if err := sw.Cmd.SetupStdio(pr1, pw2, stderr); err != nil {
    59  		sw.Cmd.Cancel() // start will not run, clear ctx
    60  		return nil, nil, err
    61  	}
    62  	sw.rwc = &rwc{} // also keep for later close
    63  	sw.rwc.WriteCloser = pw1
    64  	sw.rwc.ReadCloser = pr2
    65  
    66  	if err := sw.Cmd.Start(); err != nil {
    67  		sw.rwc.Close() // wait will not be called, clear resources
    68  		return nil, nil, err
    69  	}
    70  
    71  	return sw, sw.rwc, nil
    72  }
    73  
    74  func newServerWrapCommon(ctx context.Context, cmd string) *ServerWrap {
    75  	sw := &ServerWrap{}
    76  	args := strings.Split(cmd, " ") // TODO: escapes
    77  	sw.Cmd = osutil.NewCmd(ctx, args...)
    78  	return sw
    79  }
    80  
    81  //----------
    82  
    83  func (sw *ServerWrap) Wait() error {
    84  	if sw.rwc != nil { // can be nil if in tcp mode
    85  		// was set outside cmd, close after cmd.wait
    86  		defer sw.rwc.Close()
    87  	}
    88  
    89  	return sw.Cmd.Wait()
    90  }
    91  
    92  //----------
    93  
    94  type rwc struct {
    95  	io.ReadCloser
    96  	io.WriteCloser
    97  }
    98  
    99  func (rwc *rwc) Close() error {
   100  	me := iout.MultiError{}
   101  	me.Add(rwc.ReadCloser.Close())
   102  	me.Add(rwc.WriteCloser.Close())
   103  	return me.Result()
   104  }
   105  
   106  //----------
   107  
   108  func cmdTemplate(cmdTmpl string, host string, port int) (string, string, error) {
   109  	// build template
   110  	tmpl, err := template.New("").Parse(cmdTmpl)
   111  	if err != nil {
   112  		return "", "", err
   113  	}
   114  
   115  	// template data
   116  	type tdata struct {
   117  		Addr string
   118  		Host string
   119  		Port int
   120  	}
   121  	data := &tdata{}
   122  	data.Host = host
   123  	data.Port = port
   124  	data.Addr = fmt.Sprintf("%s:%d", host, port)
   125  
   126  	// fill template
   127  	out := &bytes.Buffer{}
   128  	if err := tmpl.Execute(out, data); err != nil {
   129  		return "", "", err
   130  	}
   131  	return out.String(), data.Addr, nil
   132  }