github.com/jmigpin/editor@v1.6.0/core/lsproto/langinstance.go (about) 1 package lsproto 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "os" 8 "time" 9 10 "github.com/jmigpin/editor/util/ctxutil" 11 "github.com/jmigpin/editor/util/iout" 12 ) 13 14 type LangInstance struct { 15 lang *LangManager 16 cli *Client 17 sw *ServerWrap // might be nil: "tcpclient" option 18 cancelCtx context.CancelFunc 19 } 20 21 func NewLangInstance(ctx context.Context, lang *LangManager) (*LangInstance, error) { 22 li := &LangInstance{lang: lang} 23 24 ctx2, cancel := context.WithCancel(ctx) 25 li.cancelCtx = cancel 26 27 if err := li.startAndInit(ctx2); err != nil { 28 cancel() 29 _ = li.Wait() 30 return nil, err 31 } 32 return li, nil 33 } 34 35 //---------- 36 37 func (li *LangInstance) startAndInit(ctx context.Context) error { 38 // start new client/server 39 if err := li.start(ctx); err != nil { 40 return err 41 } 42 // initialize client 43 if err := li.cli.Initialize(ctx); err != nil { 44 return err 45 } 46 return nil 47 } 48 49 func (li *LangInstance) start(ctx context.Context) error { 50 switch li.lang.Reg.Network { 51 case "tcp": 52 return li.startClientServerTCP(ctx) 53 case "tcpclient": 54 return li.startClientTCP(ctx, li.lang.Reg.Cmd) 55 case "stdio": 56 return li.startClientServerStdio(ctx) 57 default: 58 return fmt.Errorf("unexpected network: %v", li.lang.Reg.Network) 59 } 60 } 61 62 //---------- 63 64 func (li *LangInstance) startClientServerTCP(ctx context.Context) error { 65 // server wrap 66 sw, addr, err := StartServerWrapTCP(ctx, li.lang.Reg.Cmd, li.lang.man.serverWrapW) 67 if err != nil { 68 return err 69 } 70 li.sw = sw 71 // client 72 return li.startClientTCP(ctx, addr) 73 } 74 75 func (li *LangInstance) startClientTCP(ctx context.Context, addr string) error { 76 // client connect with retries 77 var cli *Client 78 fn := func() error { 79 cli0, err := NewClientTCP(ctx, addr, li) 80 if err != nil { 81 return err 82 } 83 cli = cli0 84 return nil 85 } 86 lateFn := func(err error) { 87 if err != nil { 88 // no connection close, it was handled already on late error 89 err = fmt.Errorf("call late: %w", err) 90 li.lang.PrintWrapError(err) 91 } 92 } 93 retryPause := 300 * time.Millisecond 94 err := ctxutil.Retry(ctx, retryPause, "clienttcp", fn, lateFn) 95 if err != nil { 96 return err 97 } 98 li.cli = cli 99 return nil 100 } 101 102 func (li *LangInstance) startClientServerStdio(ctx context.Context) error { 103 var stderr io.Writer 104 if li.lang.Reg.HasOptional("stderr") { 105 // useful for testing to see the server output msgs for debug 106 stderr = os.Stderr 107 } 108 if li.lang.Reg.HasOptional("stderrmanmsg") { 109 // get server output in manager messages (editor msgs) 110 stderr = iout.FnWriter(func(p []byte) (int, error) { 111 li.lang.man.Message(string(p)) 112 return len(p), nil 113 }) 114 } 115 116 // server wrap 117 sw, rwc, err := StartServerWrapIO(ctx, li.lang.Reg.Cmd, stderr, li) 118 if err != nil { 119 return err 120 } 121 li.sw = sw 122 // client 123 cli := NewClientIO(ctx, rwc, li) 124 li.cli = cli 125 return nil 126 } 127 128 //---------- 129 130 func (li *LangInstance) Wait() error { 131 defer li.cancelCtx() 132 var me iout.MultiError 133 if li.sw != nil { // might be nil: "tcpclient" option (or not started) 134 me.Add(li.sw.Wait()) 135 } 136 if li.cli != nil { // might be nil: not started in case of sw start error 137 me.Add(li.cli.Wait()) 138 } 139 return me.Result() 140 }