github.com/xyproto/orbiton/v2@v2.65.12-0.20240516144430-e10a419274ec/run.go (about) 1 package main 2 3 import ( 4 "os" 5 "os/exec" 6 "path/filepath" 7 "strings" 8 9 "github.com/xyproto/files" 10 "github.com/xyproto/mode" 11 "github.com/xyproto/vt100" 12 ) 13 14 // CanRun checks if the current file mode supports running executables after building 15 func (e *Editor) CanRun() bool { 16 switch e.mode { 17 case mode.AIDL, mode.ASCIIDoc, mode.Amber, mode.Bazel, mode.Blank, mode.Config, mode.Email, mode.Git, mode.HIDL, mode.HTML, mode.JSON, mode.Log, mode.M4, mode.ManPage, mode.Markdown, mode.Nroff, mode.PolicyLanguage, mode.ReStructured, mode.SCDoc, mode.SQL, mode.Shader, mode.Text, mode.XML: 18 return false 19 case mode.Shell: // don't run, because it's not a good idea 20 return false 21 case mode.Zig: // TODO: Find out why running Zig programs is problematic, terminal emulator wise 22 return false 23 } 24 return true 25 } 26 27 // Run will attempt to run the corresponding output executable, given a source filename. 28 // It's an advantage if the BuildOrExport function has been successfully run first. 29 // The bool is true only if the command exited with an exit code != 0 and there is text on stderr, 30 // which implies that the error style / background color should be used when presenting the output. 31 func (e *Editor) Run() (string, bool, error) { 32 sourceFilename, err := filepath.Abs(e.filename) 33 if err != nil { 34 return "", false, err 35 } 36 37 sourceDir := filepath.Dir(sourceFilename) 38 39 pyCacheDir := filepath.Join(userCacheDir, "o", "python") 40 if noWriteToCache { 41 pyCacheDir = filepath.Join(sourceDir, "o", "python") 42 } 43 44 var cmd *exec.Cmd 45 46 // Make sure not to do anything with cmd here until it has been initialized by the switch below! 47 48 switch e.mode { 49 case mode.CMake: 50 cmd = exec.Command("cmake", "-B", "build", "-D", "CMAKE_BUILD_TYPE=Debug", "-S", sourceDir) 51 case mode.Kotlin: 52 jarName := e.exeName(sourceFilename, false) + ".jar" 53 cmd = exec.Command("java", "-jar", jarName) 54 case mode.Go: 55 cmd = exec.Command("go", "run", sourceFilename) 56 case mode.Lilypond: 57 ext := filepath.Ext(sourceFilename) 58 firstName := strings.TrimSuffix(filepath.Base(sourceFilename), ext) 59 pdfFilename := firstName + ".pdf" 60 if isDarwin() { 61 cmd = exec.Command("open", pdfFilename) 62 } else { 63 cmd = exec.Command("xdg-open", pdfFilename) 64 } 65 case mode.Lua: 66 cmd = exec.Command("lua", sourceFilename) 67 case mode.Make: 68 cmd = exec.Command("make") 69 case mode.Java: 70 cmd = exec.Command("java", "-jar", "main.jar") 71 case mode.Just: 72 cmd = exec.Command("just") 73 case mode.Python: 74 if isDarwin() { 75 cmd = exec.Command("python3", sourceFilename) 76 } else { 77 cmd = exec.Command("python", sourceFilename) 78 } 79 cmd.Env = append(cmd.Env, "PYTHONUTF8=1") 80 if !files.Exists(pyCacheDir) { 81 os.MkdirAll(pyCacheDir, 0o700) 82 } 83 cmd.Env = append(cmd.Env, "PYTHONPYCACHEPREFIX="+pyCacheDir) 84 default: 85 exeName := filepath.Join(sourceDir, e.exeName(e.filename, true)) 86 cmd = exec.Command(exeName) 87 } 88 89 cmd.Dir = sourceDir 90 91 // If inputFileWhenRunning has been specified (or is input.txt), 92 // check if that file can be used as stdin for the command to be run 93 if inputFileWhenRunning != "" { 94 inputFile, err := os.Open(inputFileWhenRunning) 95 if err != nil { 96 // Do not retry until the editor has been started again 97 inputFileWhenRunning = "" 98 } else { 99 defer inputFile.Close() 100 // Use the file as the input for stdin 101 cmd.Stdin = inputFile 102 } 103 } 104 105 output, err := cmd.CombinedOutput() 106 if err == nil { // success 107 return trimRightSpace(string(output)), false, nil 108 } 109 if len(output) > 0 { // error, but text on stdout/stderr 110 return trimRightSpace(string(output)), true, nil 111 } 112 // error and no text on stdout/stderr 113 return "", false, err 114 } 115 116 // DrawOutput will draw a pane with the 5 last lines of the given output 117 func (e *Editor) DrawOutput(c *vt100.Canvas, maxLines int, title, collectedOutput string, backgroundColor vt100.AttributeColor, repositionCursorAfterDrawing bool) { 118 minWidth := 32 119 120 // Get the last maxLine lines, and create a string slice 121 lines := strings.Split(collectedOutput, "\n") 122 if l := len(lines); l > maxLines { 123 lines = lines[l-maxLines:] 124 // Add "[...]" as the first line 125 lines = append([]string{"[...]", ""}, lines...) 126 } 127 for _, line := range lines { 128 if len(line) > minWidth { 129 minWidth = len(line) + 5 130 } 131 } 132 if minWidth > 79 { 133 minWidth = 79 134 } 135 136 // First create a box the size of the entire canvas 137 canvasBox := NewCanvasBox(c) 138 139 lowerLeftBox := NewBox() 140 lowerLeftBox.LowerLeftPlacement(canvasBox, minWidth) 141 142 if title == "" { 143 lowerLeftBox.H = 5 144 } 145 146 lowerLeftBox.Y -= 5 147 lowerLeftBox.H += 2 148 149 // Then create a list box 150 listBox := NewBox() 151 listBox.FillWithMargins(lowerLeftBox, 1, 2) 152 153 // Get the current theme for the stdout box 154 bt := e.NewBoxTheme() 155 bt.Background = &backgroundColor 156 bt.UpperEdge = bt.LowerEdge 157 158 e.DrawBox(bt, c, lowerLeftBox) 159 160 if title != "" { 161 e.DrawTitle(bt, c, lowerLeftBox, title, true) 162 } 163 164 e.DrawList(bt, c, listBox, lines, -1) 165 166 // Blit 167 c.Draw() 168 169 // Reposition the cursor 170 if repositionCursorAfterDrawing { 171 x := e.pos.ScreenX() 172 y := e.pos.ScreenY() 173 vt100.SetXY(uint(x), uint(y)) 174 } 175 }