vendor tsgo
This commit is contained in:
61
tools/tsgo/cmd/tsgo/api.go
Normal file
61
tools/tsgo/cmd/tsgo/api.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/api"
|
||||
"github.com/microsoft/typescript-go/internal/bundled"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
)
|
||||
|
||||
func runAPI(args []string) int {
|
||||
flag := flag.NewFlagSet("api", flag.ContinueOnError)
|
||||
cwd := flag.String("cwd", core.Must(os.Getwd()), "current working directory")
|
||||
pipePath := flag.String("pipe", "", "use named pipe or Unix domain socket for communication instead of stdio")
|
||||
callbacks := flag.String("callbacks", "", "comma-separated list of FS callbacks to enable (readFile,fileExists,directoryExists,getAccessibleEntries,realpath)")
|
||||
async := flag.Bool("async", false, "use JSON-RPC protocol instead of MessagePack (for async API)")
|
||||
timing := flag.Bool("timing", false, "collect per-request server processing time, folded into the client's timing snapshot")
|
||||
if err := flag.Parse(args); err != nil {
|
||||
return 2
|
||||
}
|
||||
|
||||
defaultLibraryPath := bundled.LibPath()
|
||||
|
||||
// Parse callbacks list
|
||||
var callbacksList []string
|
||||
if *callbacks != "" {
|
||||
callbacksList = strings.Split(*callbacks, ",")
|
||||
}
|
||||
|
||||
options := &api.StdioServerOptions{
|
||||
Err: os.Stderr,
|
||||
Cwd: *cwd,
|
||||
DefaultLibraryPath: defaultLibraryPath,
|
||||
Callbacks: callbacksList,
|
||||
Async: *async,
|
||||
CollectTiming: *timing,
|
||||
}
|
||||
if *pipePath != "" {
|
||||
options.PipePath = *pipePath
|
||||
} else {
|
||||
options.In = os.Stdin
|
||||
options.Out = os.Stdout
|
||||
}
|
||||
|
||||
s := api.NewStdioServer(options)
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
if err := s.Run(ctx); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
22
tools/tsgo/cmd/tsgo/enablevtprocessing_windows.go
Normal file
22
tools/tsgo/cmd/tsgo/enablevtprocessing_windows.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func init() {
|
||||
h, err := windows.GetStdHandle(windows.STD_OUTPUT_HANDLE)
|
||||
if err != nil || h == windows.InvalidHandle {
|
||||
return
|
||||
}
|
||||
fileType, err := windows.GetFileType(h)
|
||||
if err != nil || fileType == windows.FILE_TYPE_CHAR {
|
||||
var mode uint32
|
||||
if err := windows.GetConsoleMode(h, &mode); err != nil {
|
||||
return
|
||||
}
|
||||
if mode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 {
|
||||
_ = windows.SetConsoleMode(h, mode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
}
|
||||
}
|
||||
}
|
||||
9
tools/tsgo/cmd/tsgo/isprocessalive_other.go
Normal file
9
tools/tsgo/cmd/tsgo/isprocessalive_other.go
Normal file
@@ -0,0 +1,9 @@
|
||||
//go:build !unix && !windows
|
||||
|
||||
package main
|
||||
|
||||
const processAliveSupported = false
|
||||
|
||||
func isProcessAlive(pid int) bool {
|
||||
panic("isProcessAlive is not supported on this platform")
|
||||
}
|
||||
25
tools/tsgo/cmd/tsgo/isprocessalive_unix.go
Normal file
25
tools/tsgo/cmd/tsgo/isprocessalive_unix.go
Normal file
@@ -0,0 +1,25 @@
|
||||
//go:build unix
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
const processAliveSupported = true
|
||||
|
||||
// isProcessAlive checks if a process with the given PID is still running.
|
||||
// On Unix, FindProcess always succeeds, so we send signal 0 to probe the
|
||||
// process. If the signal returns nil or EPERM, the process exists (EPERM
|
||||
// means it exists but we lack permission to signal it). ESRCH or any
|
||||
// other error indicates the process is gone.
|
||||
func isProcessAlive(pid int) bool {
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
err = proc.Signal(syscall.Signal(0))
|
||||
return err == nil || errors.Is(err, syscall.EPERM)
|
||||
}
|
||||
26
tools/tsgo/cmd/tsgo/isprocessalive_windows.go
Normal file
26
tools/tsgo/cmd/tsgo/isprocessalive_windows.go
Normal file
@@ -0,0 +1,26 @@
|
||||
//go:build windows
|
||||
|
||||
package main
|
||||
|
||||
import "syscall"
|
||||
|
||||
const processAliveSupported = true
|
||||
|
||||
// isProcessAlive checks if a process with the given PID is still running.
|
||||
// On Windows, we open the process with SYNCHRONIZE access and use
|
||||
// WaitForSingleObject with a zero timeout. If the wait times out, the
|
||||
// process is still running. If the object is signaled, it has exited.
|
||||
func isProcessAlive(pid int) bool {
|
||||
const SYNCHRONIZE = 0x00100000
|
||||
handle, err := syscall.OpenProcess(SYNCHRONIZE, false, uint32(pid))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer func() { _ = syscall.CloseHandle(handle) }()
|
||||
ret, err := syscall.WaitForSingleObject(handle, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
const WAIT_TIMEOUT = 258
|
||||
return ret == WAIT_TIMEOUT
|
||||
}
|
||||
108
tools/tsgo/cmd/tsgo/lsp.go
Normal file
108
tools/tsgo/cmd/tsgo/lsp.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/bundled"
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/lsp"
|
||||
"github.com/microsoft/typescript-go/internal/pprof"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/osvfs"
|
||||
)
|
||||
|
||||
func runLSP(args []string) int {
|
||||
flag := flag.NewFlagSet("lsp", flag.ContinueOnError)
|
||||
stdio := flag.Bool("stdio", false, "use stdio for communication")
|
||||
pprofDir := flag.String("pprofDir", "", "Generate pprof CPU/memory profiles to the given directory.")
|
||||
pipe := flag.String("pipe", "", "use named pipe for communication")
|
||||
_ = pipe
|
||||
socket := flag.String("socket", "", "use socket for communication")
|
||||
_ = socket
|
||||
if err := flag.Parse(args); err != nil {
|
||||
return 2
|
||||
}
|
||||
|
||||
if !*stdio {
|
||||
fmt.Fprintln(os.Stderr, "only stdio is supported")
|
||||
return 1
|
||||
}
|
||||
|
||||
if *pprofDir != "" {
|
||||
fmt.Fprintf(os.Stderr, "pprof profiles will be written to: %v\n", *pprofDir)
|
||||
profileSession := pprof.BeginProfiling(*pprofDir, os.Stderr)
|
||||
defer profileSession.Stop()
|
||||
}
|
||||
|
||||
fs := bundled.WrapFS(osvfs.FS())
|
||||
defaultLibraryPath := bundled.LibPath()
|
||||
typingsLocation := osvfs.GetGlobalTypingsCacheLocation()
|
||||
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
s := lsp.NewServer(&lsp.ServerOptions{
|
||||
In: lsp.ToReader(os.Stdin),
|
||||
Out: lsp.ToWriter(os.Stdout),
|
||||
Err: os.Stderr,
|
||||
Cwd: core.Must(os.Getwd()),
|
||||
FS: fs,
|
||||
DefaultLibraryPath: defaultLibraryPath,
|
||||
TypingsLocation: typingsLocation,
|
||||
NpmInstall: func(cwd string, args []string) ([]byte, error) {
|
||||
cmd := exec.Command("npm", args...)
|
||||
cmd.Dir = cwd
|
||||
return cmd.Output()
|
||||
},
|
||||
ProgressDelay: 250 * time.Millisecond,
|
||||
SetParentProcessID: newParentProcessWatchdog(ctx, stop),
|
||||
})
|
||||
|
||||
if err := s.Run(ctx); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// newParentProcessWatchdog returns a SetParentProcessID callback if the platform
|
||||
// supports process-alive checking, or nil otherwise.
|
||||
func newParentProcessWatchdog(ctx context.Context, stop context.CancelFunc) func(int) {
|
||||
if !processAliveSupported {
|
||||
return nil
|
||||
}
|
||||
return func(parentPID int) {
|
||||
startParentProcessWatchdog(ctx, stop, parentPID)
|
||||
}
|
||||
}
|
||||
|
||||
// startParentProcessWatchdog starts a goroutine that monitors the parent process
|
||||
// and cancels the context if the parent dies. This prevents orphaned language
|
||||
// server processes when the editor crashes or is killed.
|
||||
func startParentProcessWatchdog(ctx context.Context, stop context.CancelFunc, parentPID int) {
|
||||
if parentPID <= 0 {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
if !isProcessAlive(parentPID) {
|
||||
fmt.Fprintf(os.Stderr, "Parent process %d has exited, shutting down.\n", parentPID)
|
||||
stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
32
tools/tsgo/cmd/tsgo/main.go
Normal file
32
tools/tsgo/cmd/tsgo/main.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/core"
|
||||
"github.com/microsoft/typescript-go/internal/execute"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Exit(runMain())
|
||||
}
|
||||
|
||||
func runMain() int {
|
||||
core.ApplyDebugStackLimit()
|
||||
args := os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "--lsp":
|
||||
return runLSP(args[1:])
|
||||
case "--api":
|
||||
return runAPI(args[1:])
|
||||
}
|
||||
}
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
result := execute.CommandLine(ctx, newSystem(), args, nil)
|
||||
return int(result.Status)
|
||||
}
|
||||
76
tools/tsgo/cmd/tsgo/sys.go
Normal file
76
tools/tsgo/cmd/tsgo/sys.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/microsoft/typescript-go/internal/bundled"
|
||||
"github.com/microsoft/typescript-go/internal/execute/tsc"
|
||||
"github.com/microsoft/typescript-go/internal/tspath"
|
||||
"github.com/microsoft/typescript-go/internal/vfs"
|
||||
"github.com/microsoft/typescript-go/internal/vfs/osvfs"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
type osSys struct {
|
||||
writer io.Writer
|
||||
fs vfs.FS
|
||||
defaultLibraryPath string
|
||||
cwd string
|
||||
start time.Time
|
||||
}
|
||||
|
||||
func (s *osSys) SinceStart() time.Duration {
|
||||
return time.Since(s.start)
|
||||
}
|
||||
|
||||
func (s *osSys) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func (s *osSys) FS() vfs.FS {
|
||||
return s.fs
|
||||
}
|
||||
|
||||
func (s *osSys) DefaultLibraryPath() string {
|
||||
return s.defaultLibraryPath
|
||||
}
|
||||
|
||||
func (s *osSys) GetCurrentDirectory() string {
|
||||
return s.cwd
|
||||
}
|
||||
|
||||
func (s *osSys) Writer() io.Writer {
|
||||
return s.writer
|
||||
}
|
||||
|
||||
func (s *osSys) WriteOutputIsTTY() bool {
|
||||
return term.IsTerminal(int(os.Stdout.Fd()))
|
||||
}
|
||||
|
||||
func (s *osSys) GetWidthOfTerminal() int {
|
||||
width, _, _ := term.GetSize(int(os.Stdout.Fd()))
|
||||
return width
|
||||
}
|
||||
|
||||
func (s *osSys) GetEnvironmentVariable(name string) string {
|
||||
return os.Getenv(name)
|
||||
}
|
||||
|
||||
func newSystem() *osSys {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error getting current directory: %v\n", err)
|
||||
os.Exit(int(tsc.ExitStatusInvalidProject_OutputsSkipped))
|
||||
}
|
||||
|
||||
return &osSys{
|
||||
cwd: tspath.NormalizePath(cwd),
|
||||
fs: bundled.WrapFS(osvfs.FS()),
|
||||
defaultLibraryPath: bundled.LibPath(),
|
||||
writer: os.Stdout,
|
||||
start: time.Now(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user