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 }