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  }