github.com/Ryooooooga/lazygit@v0.8.1/pkg/gui/view_helpers.go (about) 1 package gui 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/jesseduffield/gocui" 9 "github.com/jesseduffield/lazygit/pkg/utils" 10 "github.com/spkg/bom" 11 ) 12 13 var cyclableViews = []string{"status", "files", "branches", "commits", "stash"} 14 15 func (gui *Gui) refreshSidePanels(g *gocui.Gui) error { 16 if err := gui.refreshBranches(g); err != nil { 17 return err 18 } 19 if err := gui.refreshFiles(); err != nil { 20 return err 21 } 22 if err := gui.refreshCommits(g); err != nil { 23 return err 24 } 25 26 return gui.refreshStashEntries(g) 27 } 28 29 func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error { 30 var focusedViewName string 31 if v == nil || v.Name() == cyclableViews[len(cyclableViews)-1] { 32 focusedViewName = cyclableViews[0] 33 } else { 34 // if we're in the commitFiles view we'll act like we're in the commits view 35 viewName := v.Name() 36 if viewName == "commitFiles" { 37 viewName = "commits" 38 } 39 for i := range cyclableViews { 40 if viewName == cyclableViews[i] { 41 focusedViewName = cyclableViews[i+1] 42 break 43 } 44 if i == len(cyclableViews)-1 { 45 message := gui.Tr.TemplateLocalize( 46 "IssntListOfViews", 47 Teml{ 48 "name": viewName, 49 }, 50 ) 51 gui.Log.Info(message) 52 return nil 53 } 54 } 55 } 56 focusedView, err := g.View(focusedViewName) 57 if err != nil { 58 panic(err) 59 } 60 return gui.switchFocus(g, v, focusedView) 61 } 62 63 func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error { 64 var focusedViewName string 65 if v == nil || v.Name() == cyclableViews[0] { 66 focusedViewName = cyclableViews[len(cyclableViews)-1] 67 } else { 68 // if we're in the commitFiles view we'll act like we're in the commits view 69 viewName := v.Name() 70 if viewName == "commitFiles" { 71 viewName = "commits" 72 } 73 for i := range cyclableViews { 74 if viewName == cyclableViews[i] { 75 focusedViewName = cyclableViews[i-1] // TODO: make this work properly 76 break 77 } 78 if i == len(cyclableViews)-1 { 79 message := gui.Tr.TemplateLocalize( 80 "IssntListOfViews", 81 Teml{ 82 "name": viewName, 83 }, 84 ) 85 gui.Log.Info(message) 86 return nil 87 } 88 } 89 } 90 focusedView, err := g.View(focusedViewName) 91 if err != nil { 92 panic(err) 93 } 94 return gui.switchFocus(g, v, focusedView) 95 } 96 97 func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error { 98 switch v.Name() { 99 case "menu": 100 return gui.handleMenuSelect(g, v) 101 case "status": 102 return gui.handleStatusSelect(g, v) 103 case "files": 104 return gui.handleFileSelect(g, v, false) 105 case "branches": 106 return gui.handleBranchSelect(g, v) 107 case "commits": 108 return gui.handleCommitSelect(g, v) 109 case "commitFiles": 110 return gui.handleCommitFileSelect(g, v) 111 case "stash": 112 return gui.handleStashEntrySelect(g, v) 113 case "confirmation": 114 return nil 115 case "commitMessage": 116 return gui.handleCommitFocused(g, v) 117 case "credentials": 118 return gui.handleCredentialsViewFocused(g, v) 119 case "main": 120 if gui.State.Contexts["main"] == "merging" { 121 return gui.refreshMergePanel() 122 } 123 v.Highlight = false 124 return nil 125 default: 126 panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement")) 127 } 128 } 129 130 func (gui *Gui) returnFocus(g *gocui.Gui, v *gocui.View) error { 131 previousView, err := g.View(gui.State.PreviousView) 132 if err != nil { 133 // always fall back to files view if there's no 'previous' view stored 134 previousView, err = g.View("files") 135 if err != nil { 136 gui.Log.Error(err) 137 } 138 } 139 return gui.switchFocus(g, v, previousView) 140 } 141 142 // pass in oldView = nil if you don't want to be able to return to your old view 143 // TODO: move some of this logic into our onFocusLost and onFocus hooks 144 func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { 145 // we assume we'll never want to return focus to a popup panel i.e. 146 // we should never stack popup panels 147 if oldView != nil && !gui.isPopupPanel(oldView.Name()) { 148 gui.State.PreviousView = oldView.Name() 149 } 150 151 gui.Log.Info("setting highlight to true for view" + newView.Name()) 152 message := gui.Tr.TemplateLocalize( 153 "newFocusedViewIs", 154 Teml{ 155 "newFocusedView": newView.Name(), 156 }, 157 ) 158 gui.Log.Info(message) 159 if _, err := g.SetCurrentView(newView.Name()); err != nil { 160 return err 161 } 162 if _, err := g.SetViewOnTop(newView.Name()); err != nil { 163 return err 164 } 165 166 g.Cursor = newView.Editable 167 168 if err := gui.renderPanelOptions(); err != nil { 169 return err 170 } 171 172 return gui.newLineFocused(g, newView) 173 } 174 175 func (gui *Gui) resetOrigin(v *gocui.View) error { 176 _ = v.SetCursor(0, 0) 177 return v.SetOrigin(0, 0) 178 } 179 180 // if the cursor down past the last item, move it to the last line 181 func (gui *Gui) focusPoint(cx int, cy int, lineCount int, v *gocui.View) error { 182 if cy < 0 || cy > lineCount { 183 return nil 184 } 185 ox, oy := v.Origin() 186 _, height := v.Size() 187 188 ly := height - 1 189 if ly == -1 { 190 ly = 0 191 } 192 193 // if line is above origin, move origin and set cursor to zero 194 // if line is below origin + height, move origin and set cursor to max 195 // otherwise set cursor to value - origin 196 if ly > lineCount { 197 _ = v.SetCursor(cx, cy) 198 _ = v.SetOrigin(ox, 0) 199 } else if cy < oy { 200 _ = v.SetCursor(cx, 0) 201 _ = v.SetOrigin(ox, cy) 202 } else if cy > oy+ly { 203 _ = v.SetCursor(cx, ly) 204 _ = v.SetOrigin(ox, cy-ly) 205 } else { 206 _ = v.SetCursor(cx, cy-oy) 207 } 208 return nil 209 } 210 211 func (gui *Gui) cleanString(s string) string { 212 output := string(bom.Clean([]byte(s))) 213 return utils.NormalizeLinefeeds(output) 214 } 215 216 func (gui *Gui) setViewContent(g *gocui.Gui, v *gocui.View, s string) error { 217 v.Clear() 218 fmt.Fprint(v, gui.cleanString(s)) 219 return nil 220 } 221 222 // renderString resets the origin of a view and sets its content 223 func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error { 224 g.Update(func(*gocui.Gui) error { 225 v, err := g.View(viewName) 226 if err != nil { 227 return nil // return gracefully if view has been deleted 228 } 229 if err := v.SetOrigin(0, 0); err != nil { 230 return err 231 } 232 return gui.setViewContent(gui.g, v, s) 233 }) 234 return nil 235 } 236 237 func (gui *Gui) optionsMapToString(optionsMap map[string]string) string { 238 optionsArray := make([]string, 0) 239 for key, description := range optionsMap { 240 optionsArray = append(optionsArray, key+": "+description) 241 } 242 sort.Strings(optionsArray) 243 return strings.Join(optionsArray, ", ") 244 } 245 246 func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error { 247 return gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap)) 248 } 249 250 // TODO: refactor properly 251 // i'm so sorry but had to add this getBranchesView 252 func (gui *Gui) getFilesView() *gocui.View { 253 v, _ := gui.g.View("files") 254 return v 255 } 256 257 func (gui *Gui) getCommitsView() *gocui.View { 258 v, _ := gui.g.View("commits") 259 return v 260 } 261 262 func (gui *Gui) getCommitMessageView() *gocui.View { 263 v, _ := gui.g.View("commitMessage") 264 return v 265 } 266 267 func (gui *Gui) getBranchesView() *gocui.View { 268 v, _ := gui.g.View("branches") 269 return v 270 } 271 272 func (gui *Gui) getMainView() *gocui.View { 273 v, _ := gui.g.View("main") 274 return v 275 } 276 277 func (gui *Gui) getStashView() *gocui.View { 278 v, _ := gui.g.View("stash") 279 return v 280 } 281 282 func (gui *Gui) getCommitFilesView() *gocui.View { 283 v, _ := gui.g.View("commitFiles") 284 return v 285 } 286 287 func (gui *Gui) trimmedContent(v *gocui.View) string { 288 return strings.TrimSpace(v.Buffer()) 289 } 290 291 func (gui *Gui) currentViewName() string { 292 currentView := gui.g.CurrentView() 293 return currentView.Name() 294 } 295 296 func (gui *Gui) resizeCurrentPopupPanel(g *gocui.Gui) error { 297 v := g.CurrentView() 298 if gui.isPopupPanel(v.Name()) { 299 return gui.resizePopupPanel(g, v) 300 } 301 return nil 302 } 303 304 func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { 305 // If the confirmation panel is already displayed, just resize the width, 306 // otherwise continue 307 content := v.Buffer() 308 x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Wrap, content) 309 vx0, vy0, vx1, vy1 := v.Dimensions() 310 if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 { 311 return nil 312 } 313 gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel")) 314 _, err := g.SetView(v.Name(), x0, y0, x1, y1, 0) 315 return err 316 } 317 318 // generalFocusLine takes a lineNumber to focus, and a bottomLine to ensure we can see 319 func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View) error { 320 _, height := v.Size() 321 overScroll := bottomLine - height + 1 322 if overScroll < 0 { 323 overScroll = 0 324 } 325 if err := v.SetOrigin(0, overScroll); err != nil { 326 return err 327 } 328 if err := v.SetCursor(0, lineNumber-overScroll); err != nil { 329 return err 330 } 331 return nil 332 } 333 334 func (gui *Gui) changeSelectedLine(line *int, total int, up bool) { 335 if up { 336 if *line == -1 || *line == 0 { 337 return 338 } 339 340 *line -= 1 341 } else { 342 if *line == -1 || *line == total-1 { 343 return 344 } 345 346 *line += 1 347 } 348 } 349 350 func (gui *Gui) refreshSelectedLine(line *int, total int) { 351 if *line == -1 && total > 0 { 352 *line = 0 353 } else if total-1 < *line { 354 *line = total - 1 355 } 356 } 357 358 func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error { 359 gui.g.Update(func(g *gocui.Gui) error { 360 isFocused := gui.g.CurrentView().Name() == v.Name() 361 list, err := utils.RenderList(items, isFocused) 362 if err != nil { 363 return gui.createErrorPanel(gui.g, err.Error()) 364 } 365 v.Clear() 366 fmt.Fprint(v, list) 367 return nil 368 }) 369 return nil 370 } 371 372 func (gui *Gui) renderPanelOptions() error { 373 currentView := gui.g.CurrentView() 374 switch currentView.Name() { 375 case "menu": 376 return gui.renderMenuOptions() 377 case "main": 378 if gui.State.Contexts["main"] == "merging" { 379 return gui.renderMergeOptions() 380 } 381 } 382 return gui.renderGlobalOptions() 383 } 384 385 func (gui *Gui) handleFocusView(g *gocui.Gui, v *gocui.View) error { 386 _, err := gui.g.SetCurrentView(v.Name()) 387 return err 388 } 389 390 func (gui *Gui) isPopupPanel(viewName string) bool { 391 return viewName == "commitMessage" || viewName == "credentials" || viewName == "confirmation" || viewName == "menu" 392 } 393 394 func (gui *Gui) popupPanelFocused() bool { 395 return gui.isPopupPanel(gui.currentViewName()) 396 }