1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-12-14 11:23:09 +02:00
lazygit/pkg/tasks/tasks.go

389 lines
9.8 KiB
Go
Raw Normal View History

allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
package tasks
import (
"bufio"
"fmt"
"io"
"os/exec"
"strings"
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
"sync"
"time"
Use first class task objects instead of global counter The global counter approach is easy to understand but it's brittle and depends on implicit behaviour that is not very discoverable. With a global counter, if any goroutine accidentally decrements the counter twice, we'll think lazygit is idle when it's actually busy. Likewise if a goroutine accidentally increments the counter twice we'll think lazygit is busy when it's actually idle. With the new approach we have a map of tasks where each task can either be busy or not. We create a new task and add it to the map when we spawn a worker goroutine (among other things) and we remove it once the task is done. The task can also be paused and continued for situations where we switch back and forth between running a program and asking for user input. In order for this to work with `git push` (and other commands that require credentials) we need to obtain the task from gocui when we create the worker goroutine, and then pass it along to the commands package to pause/continue the task as required. This is MUCH more discoverable than the old approach which just decremented and incremented the global counter from within the commands package, but it's at the cost of expanding some function signatures (arguably a good thing). Likewise, whenever you want to call WithWaitingStatus or WithLoaderPanel the callback will now have access to the task for pausing/ continuing. We only need to actually make use of this functionality in a couple of places so it's a high price to pay, but I don't know if I want to introduce a WithWaitingStatusTask and WithLoaderPanelTask function (open to suggestions).
2023-07-09 03:32:27 +02:00
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
2020-10-07 12:19:38 +02:00
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sasha-s/go-deadlock"
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
"github.com/sirupsen/logrus"
)
2022-05-07 07:42:36 +02:00
// This file revolves around running commands that will be output to the main panel
// in the gui. If we're flicking through the commits panel, we want to invoke a
// `git show` command for each commit, but we don't want to read the entire output
// at once (because that would slow things down); we just want to fill the panel
// and then read more as the user scrolls down. We also want to ensure that we're only
// ever running one `git show` command at time, and that we only have one command
// writing its output to the main panel at a time.
2021-11-02 07:39:15 +02:00
const THROTTLE_TIME = time.Millisecond * 30
2021-11-02 12:23:04 +02:00
// we use this to check if the system is under stress right now. Hopefully this makes sense on other machines
const COMMAND_START_THRESHOLD = time.Millisecond * 10
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
type ViewBufferManager struct {
2021-11-07 04:25:06 +02:00
// this blocks until the task has been properly stopped
stopCurrentTask func()
// this is what we write the output of the task to. It's typically a view
writer io.Writer
waitingMutex deadlock.Mutex
taskIDMutex deadlock.Mutex
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
Log *logrus.Entry
2022-03-19 00:38:49 +02:00
newTaskID int
readLines chan LinesToRead
2021-10-17 10:01:02 +02:00
taskKey string
onNewKey func()
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
// beforeStart is the function that is called before starting a new task
2021-10-17 10:01:02 +02:00
beforeStart func()
refreshView func()
onEndOfInput func()
2021-11-02 07:39:15 +02:00
// see docs/dev/Busy.md
// A gocui task is not the same thing as the tasks defined in this file.
// A gocui task simply represents the fact that lazygit is busy doing something,
// whereas the tasks in this file are about rendering content to a view.
newGocuiTask func() gocui.Task
2021-11-02 07:39:15 +02:00
// if the user flicks through a heap of items, with each one
// spawning a process to render something to the main view,
// it can slow things down quite a bit. In these situations we
// want to throttle the spawning of processes.
throttle bool
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
}
type LinesToRead struct {
// Total number of lines to read
Total int
// Number of lines after which we have read enough to fill the view, and can
// do an initial refresh. Only set for the initial read request; -1 for
// subsequent requests.
InitialRefreshAfter int
}
2021-10-17 10:01:02 +02:00
func (m *ViewBufferManager) GetTaskKey() string {
return m.taskKey
}
func NewViewBufferManager(
log *logrus.Entry,
writer io.Writer,
beforeStart func(),
refreshView func(),
onEndOfInput func(),
onNewKey func(),
newGocuiTask func() gocui.Task,
2021-10-17 10:01:02 +02:00
) *ViewBufferManager {
return &ViewBufferManager{
Use first class task objects instead of global counter The global counter approach is easy to understand but it's brittle and depends on implicit behaviour that is not very discoverable. With a global counter, if any goroutine accidentally decrements the counter twice, we'll think lazygit is idle when it's actually busy. Likewise if a goroutine accidentally increments the counter twice we'll think lazygit is busy when it's actually idle. With the new approach we have a map of tasks where each task can either be busy or not. We create a new task and add it to the map when we spawn a worker goroutine (among other things) and we remove it once the task is done. The task can also be paused and continued for situations where we switch back and forth between running a program and asking for user input. In order for this to work with `git push` (and other commands that require credentials) we need to obtain the task from gocui when we create the worker goroutine, and then pass it along to the commands package to pause/continue the task as required. This is MUCH more discoverable than the old approach which just decremented and incremented the global counter from within the commands package, but it's at the cost of expanding some function signatures (arguably a good thing). Likewise, whenever you want to call WithWaitingStatus or WithLoaderPanel the callback will now have access to the task for pausing/ continuing. We only need to actually make use of this functionality in a couple of places so it's a high price to pay, but I don't know if I want to introduce a WithWaitingStatusTask and WithLoaderPanelTask function (open to suggestions).
2023-07-09 03:32:27 +02:00
Log: log,
writer: writer,
beforeStart: beforeStart,
refreshView: refreshView,
onEndOfInput: onEndOfInput,
readLines: make(chan LinesToRead, 1024),
onNewKey: onNewKey,
newGocuiTask: newGocuiTask,
2021-10-17 10:01:02 +02:00
}
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
}
2022-03-19 00:38:49 +02:00
func (self *ViewBufferManager) ReadLines(n int) {
2020-10-07 12:19:38 +02:00
go utils.Safe(func() {
self.readLines <- LinesToRead{Total: n, InitialRefreshAfter: -1}
2020-10-07 12:19:38 +02:00
})
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
}
func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), prefix string, linesToRead LinesToRead, onDoneFn func()) func(TaskOpts) error {
return func(opts TaskOpts) error {
var onDoneOnce sync.Once
var onFirstPageShownOnce sync.Once
onFirstPageShown := func() {
onFirstPageShownOnce.Do(func() {
opts.InitialContentLoaded()
})
}
onDone := func() {
if onDoneFn != nil {
onDoneOnce.Do(onDoneFn)
}
onFirstPageShown()
2021-11-07 04:25:06 +02:00
}
2022-03-19 00:38:49 +02:00
if self.throttle {
self.Log.Info("throttling task")
2021-11-02 07:39:15 +02:00
time.Sleep(THROTTLE_TIME)
}
select {
case <-opts.Stop:
onDone()
2021-11-02 07:39:15 +02:00
return nil
default:
}
startTime := time.Now()
cmd, r := start()
2021-11-02 12:23:04 +02:00
timeToStart := time.Since(startTime)
2021-11-02 07:39:15 +02:00
2020-10-07 12:19:38 +02:00
go utils.Safe(func() {
<-opts.Stop
2021-11-02 12:23:04 +02:00
// we use the time it took to start the program as a way of checking if things
// are running slow at the moment. This is admittedly a crude estimate, but
// the point is that we only want to throttle when things are running slow
// and the user is flicking through a bunch of items.
2022-03-19 00:38:49 +02:00
self.throttle = time.Since(startTime) < THROTTLE_TIME && timeToStart > COMMAND_START_THRESHOLD
if err := oscommands.Kill(cmd); err != nil {
if !strings.Contains(err.Error(), "process already finished") {
2022-03-19 00:38:49 +02:00
self.Log.Errorf("error when running cmd task: %v", err)
}
2020-03-01 03:30:48 +02:00
}
2021-11-07 04:25:06 +02:00
// for pty's we need to call onDone here so that cmd.Wait() doesn't block forever
onDone()
2020-10-07 12:19:38 +02:00
})
2020-03-01 03:30:48 +02:00
loadingMutex := deadlock.Mutex{}
2020-03-01 03:30:48 +02:00
// not sure if it's the right move to redefine this or not
self.readLines = make(chan LinesToRead, 1024)
2020-03-01 03:30:48 +02:00
2020-03-03 13:41:35 +02:00
done := make(chan struct{})
2021-11-07 04:25:06 +02:00
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanLines)
2020-03-01 03:30:48 +02:00
lineChan := make(chan []byte)
lineWrittenChan := make(chan struct{})
2023-07-22 01:13:54 +02:00
// We're reading from the scanner in a separate goroutine because on windows
// if running git through a shim, we sometimes kill the parent process without
// killing its children, meaning the scanner blocks forever. This solution
// leaves us with a dead goroutine, but it's better than blocking all
// rendering to main views.
go utils.Safe(func() {
defer close(lineChan)
2023-07-22 01:13:54 +02:00
for scanner.Scan() {
select {
case <-opts.Stop:
return
case lineChan <- scanner.Bytes():
// We need to confirm the data has been fed into the view before we
// pull more from the scanner because the scanner uses the same backing
// array and we don't want to be mutating that while it's being written
<-lineWrittenChan
}
2023-07-22 01:13:54 +02:00
}
})
2021-11-07 04:25:06 +02:00
loaded := false
2020-03-01 03:30:48 +02:00
2021-11-07 04:25:06 +02:00
go utils.Safe(func() {
ticker := time.NewTicker(time.Millisecond * 200)
defer ticker.Stop()
select {
case <-opts.Stop:
2021-11-07 04:25:06 +02:00
return
case <-ticker.C:
loadingMutex.Lock()
if !loaded {
2022-03-19 00:38:49 +02:00
self.beforeStart()
_, _ = self.writer.Write([]byte("loading..."))
self.refreshView()
2020-03-01 03:30:48 +02:00
}
2021-11-07 04:25:06 +02:00
loadingMutex.Unlock()
}
})
2020-03-01 03:30:48 +02:00
2021-11-07 04:25:06 +02:00
go utils.Safe(func() {
2023-04-02 07:44:05 +02:00
isViewStale := true
writeToView := func(content []byte) {
isViewStale = true
2023-07-10 04:09:32 +02:00
_, _ = self.writer.Write(content)
2023-04-02 07:44:05 +02:00
}
refreshViewIfStale := func() {
if isViewStale {
self.refreshView()
isViewStale = false
}
}
2020-03-01 03:30:48 +02:00
outer:
for {
select {
case <-opts.Stop:
2021-11-07 04:25:06 +02:00
break outer
2022-03-19 00:38:49 +02:00
case linesToRead := <-self.readLines:
for i := 0; i < linesToRead.Total; i++ {
2023-07-22 01:13:54 +02:00
var ok bool
var line []byte
2021-11-07 04:25:06 +02:00
select {
case <-opts.Stop:
2021-11-07 04:25:06 +02:00
break outer
case line, ok = <-lineChan:
2023-07-22 01:13:54 +02:00
break
2021-11-07 04:25:06 +02:00
}
2020-03-01 03:30:48 +02:00
loadingMutex.Lock()
if !loaded {
2022-03-19 00:38:49 +02:00
self.beforeStart()
if prefix != "" {
2023-04-02 07:44:05 +02:00
writeToView([]byte(prefix))
}
2020-03-01 03:30:48 +02:00
loaded = true
}
loadingMutex.Unlock()
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
if !ok {
// if we're here then there's nothing left to scan from the source
// so we're at the EOF and can flush the stale content
2022-03-19 00:38:49 +02:00
self.onEndOfInput()
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
break outer
}
2023-07-22 01:13:54 +02:00
writeToView(append(line, '\n'))
lineWrittenChan <- struct{}{}
if i+1 == linesToRead.InitialRefreshAfter {
// We have read enough lines to fill the view, so do a first refresh
// here to show what we have. Continue reading and refresh again at
// the end to make sure the scrollbar has the right size.
2023-04-02 07:44:05 +02:00
refreshViewIfStale()
}
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
}
2023-04-02 07:44:05 +02:00
refreshViewIfStale()
onFirstPageShown()
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
}
}
2023-04-02 07:44:05 +02:00
refreshViewIfStale()
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
if err := cmd.Wait(); err != nil {
// it's fine if we've killed this program ourselves
if !strings.Contains(err.Error(), "signal: killed") {
self.Log.Errorf("Unexpected error when running cmd task: %v", err)
}
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
}
// calling this here again in case the program ended on its own accord
onDone()
2020-03-01 03:30:48 +02:00
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
close(done)
close(lineWrittenChan)
2020-10-07 12:19:38 +02:00
})
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
2022-03-19 00:38:49 +02:00
self.readLines <- linesToRead
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
<-done
return nil
}
}
// Close closes the task manager, killing whatever task may currently be running
2022-03-19 00:38:49 +02:00
func (self *ViewBufferManager) Close() {
if self.stopCurrentTask == nil {
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
return
}
2020-03-01 03:30:48 +02:00
c := make(chan struct{})
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
2020-10-07 12:19:38 +02:00
go utils.Safe(func() {
2022-03-19 00:38:49 +02:00
self.stopCurrentTask()
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
c <- struct{}{}
2020-10-07 12:19:38 +02:00
})
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
select {
case <-c:
return
case <-time.After(3 * time.Second):
fmt.Println("cannot kill child process")
}
}
// different kinds of tasks:
// 1) command based, where the manager can be asked to read more lines, but the command can be killed
// 2) string based, where the manager can also be asked to read more lines
type TaskOpts struct {
// Channel that tells the task to stop, because another task wants to run.
Stop chan struct{}
// Only for tasks which are long-running, where we read more lines sporadically.
// We use this to keep track of when a user's action is complete (i.e. all views
// have been refreshed to display the results of their action)
InitialContentLoaded func()
}
func (self *ViewBufferManager) NewTask(f func(TaskOpts) error, key string) error {
2023-07-10 04:09:32 +02:00
gocuiTask := self.newGocuiTask()
Use first class task objects instead of global counter The global counter approach is easy to understand but it's brittle and depends on implicit behaviour that is not very discoverable. With a global counter, if any goroutine accidentally decrements the counter twice, we'll think lazygit is idle when it's actually busy. Likewise if a goroutine accidentally increments the counter twice we'll think lazygit is busy when it's actually idle. With the new approach we have a map of tasks where each task can either be busy or not. We create a new task and add it to the map when we spawn a worker goroutine (among other things) and we remove it once the task is done. The task can also be paused and continued for situations where we switch back and forth between running a program and asking for user input. In order for this to work with `git push` (and other commands that require credentials) we need to obtain the task from gocui when we create the worker goroutine, and then pass it along to the commands package to pause/continue the task as required. This is MUCH more discoverable than the old approach which just decremented and incremented the global counter from within the commands package, but it's at the cost of expanding some function signatures (arguably a good thing). Likewise, whenever you want to call WithWaitingStatus or WithLoaderPanel the callback will now have access to the task for pausing/ continuing. We only need to actually make use of this functionality in a couple of places so it's a high price to pay, but I don't know if I want to introduce a WithWaitingStatusTask and WithLoaderPanelTask function (open to suggestions).
2023-07-09 03:32:27 +02:00
var completeTaskOnce sync.Once
2023-07-10 04:09:32 +02:00
completeGocuiTask := func() {
Use first class task objects instead of global counter The global counter approach is easy to understand but it's brittle and depends on implicit behaviour that is not very discoverable. With a global counter, if any goroutine accidentally decrements the counter twice, we'll think lazygit is idle when it's actually busy. Likewise if a goroutine accidentally increments the counter twice we'll think lazygit is busy when it's actually idle. With the new approach we have a map of tasks where each task can either be busy or not. We create a new task and add it to the map when we spawn a worker goroutine (among other things) and we remove it once the task is done. The task can also be paused and continued for situations where we switch back and forth between running a program and asking for user input. In order for this to work with `git push` (and other commands that require credentials) we need to obtain the task from gocui when we create the worker goroutine, and then pass it along to the commands package to pause/continue the task as required. This is MUCH more discoverable than the old approach which just decremented and incremented the global counter from within the commands package, but it's at the cost of expanding some function signatures (arguably a good thing). Likewise, whenever you want to call WithWaitingStatus or WithLoaderPanel the callback will now have access to the task for pausing/ continuing. We only need to actually make use of this functionality in a couple of places so it's a high price to pay, but I don't know if I want to introduce a WithWaitingStatusTask and WithLoaderPanelTask function (open to suggestions).
2023-07-09 03:32:27 +02:00
completeTaskOnce.Do(func() {
2023-07-10 04:09:32 +02:00
gocuiTask.Done()
})
}
2020-10-07 12:19:38 +02:00
go utils.Safe(func() {
2023-07-10 04:09:32 +02:00
defer completeGocuiTask()
2022-03-19 00:38:49 +02:00
self.taskIDMutex.Lock()
self.newTaskID++
taskID := self.newTaskID
2021-10-17 10:01:02 +02:00
2022-03-19 00:38:49 +02:00
if self.GetTaskKey() != key && self.onNewKey != nil {
self.onNewKey()
2021-10-17 10:01:02 +02:00
}
2022-03-19 00:38:49 +02:00
self.taskKey = key
2021-10-17 10:01:02 +02:00
2022-03-19 00:38:49 +02:00
self.taskIDMutex.Unlock()
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
2022-03-19 00:38:49 +02:00
self.waitingMutex.Lock()
2020-03-01 03:30:48 +02:00
self.taskIDMutex.Lock()
2022-03-19 00:38:49 +02:00
if taskID < self.newTaskID {
self.waitingMutex.Unlock()
self.taskIDMutex.Unlock()
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
return
}
self.taskIDMutex.Unlock()
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
2022-03-19 00:38:49 +02:00
if self.stopCurrentTask != nil {
self.stopCurrentTask()
2021-11-07 04:25:06 +02:00
}
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
stop := make(chan struct{})
notifyStopped := make(chan struct{})
2021-11-07 04:25:06 +02:00
var once sync.Once
onStop := func() {
close(stop)
<-notifyStopped
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
}
2022-03-19 00:38:49 +02:00
self.stopCurrentTask = func() { once.Do(onStop) }
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
self.waitingMutex.Unlock()
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
2023-07-10 04:09:32 +02:00
if err := f(TaskOpts{Stop: stop, InitialContentLoaded: completeGocuiTask}); err != nil {
self.Log.Error(err) // might need an onError callback
}
close(notifyStopped)
2020-10-07 12:19:38 +02:00
})
allow fast flicking through any list panel Up till now our approach to rendering things like file diffs, branch logs, and commit patches, has been to run a command on the command line, wait for it to complete, take its output as a string, and then write that string to the main view (or secondary view e.g. when showing both staged and unstaged changes of a file). This has caused various issues. For once, if you are flicking through a list of files and an untracked file is particularly large, not only will this require lazygit to load that whole file into memory (or more accurately it's equally large diff), it also will slow down the UI thread while loading that file, and if the user continued down the list, the original command might eventually resolve and replace whatever the diff is for the newly selected file. Following what we've done in lazydocker, I've added a tasks package for when you need something done but you want it to cancel as soon as something newer comes up. Given this typically involves running a command to display to a view, I've added a viewBufferManagerMap struct to the Gui struct which allows you to define these tasks on a per-view basis. viewBufferManagers can run files and directly write the output to their view, meaning we no longer need to use so much memory. In the tasks package there is a helper method called NewCmdTask which takes a command, an initial amount of lines to read, and then runs that command, reads that number of lines, and allows for a readLines channel to tell it to read more lines. We read more lines when we scroll or resize the window. There is an adapter for the tasks package in a file called tasks_adapter which wraps the functions from the tasks package in gui-specific stuff like clearing the main view before starting the next task that wants to write to the main view. I've removed some small features as part of this work, namely the little headers that were at the top of the main view for some situations. For example, we no longer show the upstream of a selected branch. I want to re-introduce this in the future, but I didn't want to make this tasks system too complicated, and in order to facilitate a header section in the main view we'd need to have a task that gets the upstream for the current branch, writes it to the header, then tells another task to write the branch log to the main view, but without clearing inbetween. So it would get messy. I'm thinking instead of having a separate 'header' view atop the main view to render that kind of thing (which can happen in another PR) I've also simplified the 'git show' to just call 'git show' and not do anything fancy when it comes to merge commits. I considered using this tasks approach whenever we write to a view. The only thing is that the renderString method currently resets the origin of a view and I don't want to lose that. So I've left some in there that I consider harmless, but we should probably be just using tasks now for all rendering, even if it's just strings we can instantly make.
2020-01-11 05:54:59 +02:00
return nil
}