Skip to content

Commit

Permalink
Show diff preview in git window
Browse files Browse the repository at this point in the history
  • Loading branch information
Nimaoth committed May 11, 2024
1 parent 0328a95 commit 07736bc
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 35 deletions.
11 changes: 8 additions & 3 deletions scripting/editor_text_api_wasm.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1129,17 +1129,22 @@ proc getNextChange*(self: TextDocumentEditor; cursor: Cursor): Selection =
result = parseJson($res).jsonTo(typeof(result))


proc editor_text_updateDiff_void_TextDocumentEditor_wasm(arg: cstring): cstring {.
proc editor_text_updateDiff_void_TextDocumentEditor_bool_wasm(arg: cstring): cstring {.
importc.}
proc updateDiff*(self: TextDocumentEditor) =
proc updateDiff*(self: TextDocumentEditor; gotoFirstDiff: bool = false) =
var argsJson = newJArray()
argsJson.add block:
when TextDocumentEditor is JsonNode:
self
else:
self.toJson()
argsJson.add block:
when bool is JsonNode:
gotoFirstDiff
else:
gotoFirstDiff.toJson()
let argsJsonString = $argsJson
let res {.used.} = editor_text_updateDiff_void_TextDocumentEditor_wasm(
let res {.used.} = editor_text_updateDiff_void_TextDocumentEditor_bool_wasm(
argsJsonString.cstring)


Expand Down
40 changes: 31 additions & 9 deletions src/app.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1851,6 +1851,7 @@ proc pushSelectorPopup*(self: App, builder: SelectorPopupBuilder): ISelectorPopu
var popup = newSelectorPopup(self.asAppInterface, builder.scope, builder.finder, builder.previewer)
popup.scale.x = builder.scaleX
popup.scale.y = builder.scaleY
popup.previewScale = builder.previewScale

if builder.handleItemSelected.isNotNil:
popup.handleItemSelected = proc(item: FinderItem) =
Expand Down Expand Up @@ -2066,9 +2067,13 @@ proc chooseLocation*(self: App, view: string = "new") {.expose("editor").} =
fmt"Expected path or json object with path property {item}"
return

var targetSelection = location.mapIt(it.toSelection)
if popup.getPreviewSelection().getSome(selection):
targetSelection = selection.some

let editor = self.openWorkspaceFile(path, workspace)
if editor.getSome(editor) and editor of TextDocumentEditor and location.isSome:
editor.TextDocumentEditor.targetSelection = location.get.toSelection
if editor.getSome(editor) and editor of TextDocumentEditor and targetSelection.isSome:
editor.TextDocumentEditor.targetSelection = targetSelection.get
editor.TextDocumentEditor.centerCursor()

return true
Expand Down Expand Up @@ -2156,9 +2161,13 @@ proc searchGlobalInteractive*(self: App) {.expose("editor").} =
fmt"Expected path or json object with path property {item}"
return

var targetSelection = location.mapIt(it.toSelection)
if popup.getPreviewSelection().getSome(selection):
targetSelection = selection.some

let editor = self.openWorkspaceFile(path, workspace)
if editor.getSome(editor) and editor of TextDocumentEditor and location.isSome:
editor.TextDocumentEditor.targetSelection = location.get.toSelection
if editor.getSome(editor) and editor of TextDocumentEditor and targetSelection.isSome:
editor.TextDocumentEditor.targetSelection = targetSelection.get
editor.TextDocumentEditor.centerCursor()
return true

Expand Down Expand Up @@ -2188,9 +2197,13 @@ proc searchGlobal*(self: App, query: string) {.expose("editor").} =
fmt"Expected path or json object with path property {item}"
return

var targetSelection = location.mapIt(it.toSelection)
if popup.getPreviewSelection().getSome(selection):
targetSelection = selection.some

let editor = self.openWorkspaceFile(path, workspace)
if editor.getSome(editor) and editor of TextDocumentEditor and location.isSome:
editor.TextDocumentEditor.targetSelection = location.get.toSelection
if editor.getSome(editor) and editor of TextDocumentEditor and targetSelection.isSome:
editor.TextDocumentEditor.targetSelection = targetSelection.get
editor.TextDocumentEditor.centerCursor()
return true

Expand Down Expand Up @@ -2314,8 +2327,11 @@ proc chooseGitActiveFiles*(self: App, all: bool = false) {.expose("editor").} =
let source = newAsyncCallbackDataSource () => workspace.getChangedFilesFromGitAsync(all)
var finder = newFinder(source, filterAndSort=true)

var popup = newSelectorPopup(self.asAppInterface, "git".some, finder.some)
popup.scale.x = 0.35
var popup = newSelectorPopup(self.asAppInterface, "git".some, finder.some,
newWorkspaceFilePreviewer(workspace).Previewer.some)
popup.scale.x = 1
popup.scale.y = 0.9
popup.previewScale = 0.75

popup.handleItemConfirmed = proc(item: FinderItem): bool =
let fileInfo = item.data.parseJson.jsonTo(VCSFileInfo).catch:
Expand All @@ -2330,6 +2346,9 @@ proc chooseGitActiveFiles*(self: App, all: bool = false) {.expose("editor").} =
if currentVersionEditor.getSome(editor):
if editor of TextDocumentEditor:
editor.TextDocumentEditor.updateDiff()
if popup.getPreviewSelection().getSome(selection):
editor.TextDocumentEditor.selection = selection
editor.TextDocumentEditor.centerCursor()

return true

Expand Down Expand Up @@ -2436,7 +2455,10 @@ proc exploreFiles*(self: App) {.expose("editor").} =
return true

if fileInfo.isFile:
discard self.openWorkspaceFile(fileInfo.path, workspace)
if self.openWorkspaceFile(fileInfo.path, workspace).getSome(editor):
if editor of TextDocumentEditor and popup.getPreviewSelection().getSome(selection):
editor.TextDocumentEditor.selection = selection
editor.TextDocumentEditor.centerCursor()
return true
else:
currentDirectory[] = fileInfo.path
Expand Down
49 changes: 43 additions & 6 deletions src/finder/workspace_file_previewer.nim
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import std/[tables, json, options, strformat, strutils]
import misc/[util, custom_logger, delayed_task, custom_async]
import misc/[util, custom_logger, delayed_task, custom_async, myjsonutils]
import workspaces/workspace
import text/[text_editor, text_document]
import scripting_api except DocumentEditor, TextDocumentEditor, AstDocumentEditor
import finder, previewer
import vcs/vcs

logCategory "workspace-file-previewer"

Expand All @@ -15,6 +16,8 @@ type
triggerLoadTask: DelayedTask
currentPath: string
currentLocation: Option[Cursor]
currentStaged: bool
currentDiff: bool

proc newWorkspaceFilePreviewer*(workspace: WorkspaceFolder): WorkspaceFilePreviewer =
new result
Expand Down Expand Up @@ -67,6 +70,7 @@ proc loadAsync(self: WorkspaceFilePreviewer): Future[void] {.async.} =
log lvlInfo, fmt"Discard file load of 'path' because a newer one was requested"
return

editor.document.workspace = self.workspace.some
editor.document.setFileAndContent(path, content)
if location.getSome(location):
editor.selection = location.toSelection
Expand All @@ -75,6 +79,13 @@ proc loadAsync(self: WorkspaceFilePreviewer): Future[void] {.async.} =
editor.selection = (0, 0).toSelection
editor.scrollToTop()

if self.currentDiff:
self.editor.document.staged = self.currentStaged
self.editor.updateDiff(gotoFirstDiff=true)
else:
self.editor.document.staged = false
self.editor.closeDiff()

editor.markDirty()

method delayPreview*(self: WorkspaceFilePreviewer) =
Expand All @@ -85,14 +96,33 @@ method previewItem*(self: WorkspaceFilePreviewer, item: FinderItem, editor: Docu
if not (editor of TextDocumentEditor):
return

let (path, location) = item.parsePathAndLocationFromItemData.getOr:
log lvlError, fmt"Failed to preview item because of invalid data format. " &
fmt"Expected path or json object with path property {item}"
return

inc self.revision
self.editor = editor.TextDocumentEditor

var path: string
var location: Option[Cursor]

let fileInfo = item.data.parseJson.jsonTo(VCSFileInfo).some.catch: VCSFileInfo.none
if fileInfo.isSome:
path = fileInfo.get.path
else:
let pathAndLocation = item.parsePathAndLocationFromItemData.getOr:
log lvlError, fmt"Failed to preview item because of invalid data format. " &
fmt"Expected path or json object with path property {item}"
return
path = pathAndLocation[0]
location = pathAndLocation[1]

if fileInfo.getSome(fileInfo) and not fileInfo.isUntracked:
self.currentDiff = true
if fileInfo.stagedStatus != None:
self.currentStaged = true
else:
self.currentStaged = false
else:
self.currentDiff = false
self.currentStaged = false

log lvlInfo, &"[previewItem] Request preview for '{path}' at {location}"
if self.editor.document.filename == path:
if location.getSome(location):
Expand All @@ -102,6 +132,13 @@ method previewItem*(self: WorkspaceFilePreviewer, item: FinderItem, editor: Docu
self.editor.selection = (0, 0).toSelection
self.editor.scrollToTop()

if self.currentDiff:
self.editor.document.staged = self.currentStaged
self.editor.updateDiff(gotoFirstDiff=true)
else:
self.editor.document.staged = false
self.editor.closeDiff()

else:
self.currentPath = path
self.currentLocation = location
Expand Down
6 changes: 6 additions & 0 deletions src/selector_popup.nim
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type
customEventHandler: EventHandler

scale*: Vec2
previewScale*: float = 0.5

customCommands: Table[string, proc(popup: SelectorPopup, args: JsonNode): bool]

Expand Down Expand Up @@ -87,6 +88,11 @@ proc getSearchString*(self: SelectorPopup): string =
return ""
return self.textEditor.document.contentString

proc getPreviewSelection*(self: SelectorPopup): Option[Selection] =
if self.previewEditor.isNil:
return Selection.none
return self.previewEditor.selection.some

method getActiveEditor*(self: SelectorPopup): Option[DocumentEditor] =
if self.focusPreview and self.previewEditor.isNotNil:
return self.previewEditor.DocumentEditor.some
Expand Down
1 change: 1 addition & 0 deletions src/selector_popup_builder.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type
scope*: Option[string]
scaleX*: float = 0.5
scaleY*: float = 0.5
previewScale*: float = 0.5
handleItemSelected*: proc(popup: ISelectorPopup, item: FinderItem)
handleItemConfirmed*: proc(popup: ISelectorPopup, item: FinderItem): bool
handleCanceled*: proc(popup: ISelectorPopup)
Expand Down
43 changes: 31 additions & 12 deletions src/text/text_editor.nim
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type TextDocumentEditor* = ref object of DocumentEditor

diffDocument*: TextDocument
diffChanges*: Option[seq[LineMapping]]
diffRevision: int = 0

usage*: string # Unique string identifying what the editor is used for,
# e.g. command-line/preview/search-bar
Expand Down Expand Up @@ -82,8 +83,6 @@ type TextDocumentEditor* = ref object of DocumentEditor
blinkCursor: bool = true
blinkCursorTask: DelayedTask

isGitDiffInProgress: bool = false

# hover
showHoverTask: DelayedTask # for showing hover info after a delay
hideHoverTask: DelayedTask # for hiding hover info after a delay
Expand Down Expand Up @@ -205,6 +204,8 @@ proc addCustomHighlight(self: TextDocumentEditor, id: Id, selection: Selection,
tint: Color = color(1, 1, 1))
proc clearCustomHighlights(self: TextDocumentEditor, id: Id)
proc updateSearchResults(self: TextDocumentEditor)
proc centerCursor*(self: TextDocumentEditor, cursor: SelectionCursor = SelectionCursor.Config)
proc centerCursor*(self: TextDocumentEditor, cursor: Cursor)

proc clampCursor*(self: TextDocumentEditor, cursor: Cursor, includeAfter: bool = true): Cursor =
self.document.clampCursor(cursor, includeAfter)
Expand Down Expand Up @@ -1381,19 +1382,17 @@ proc getNextChange*(self: TextDocumentEditor, cursor: Cursor): Selection {.expos

return cursor.toSelection

proc updateDiffAsync*(self: TextDocumentEditor) {.async.} =
proc updateDiffAsync*(self: TextDocumentEditor, gotoFirstDiff: bool, force: bool = false) {.async.} =
if self.document.isNil:
return
if self.isGitDiffInProgress:
return

self.isGitDiffInProgress = true
defer:
self.isGitDiffInProgress = false

if self.document.workspace.isNone:
log lvlWarn, &"Can't diff file '{self.document.filename}' without workspace."
return

inc self.diffRevision
let revision = self.diffRevision

let vcs = self.document.workspace.get.getVcsForFile(self.document.filename).getOr:
log lvlWarn, fmt"[updateDiffAsync] File is not part of any vcs: '{self.document.filename}'"
return
Expand All @@ -1403,11 +1402,21 @@ proc updateDiffAsync*(self: TextDocumentEditor) {.async.} =
let relPath = self.document.workspace.mapIt(
it.getRelativePath(self.document.filename).await
).flatten.get(self.document.filename)
if self.diffRevision > revision:
return

if self.document.staged:
let committedFileContent = vcs.getCommittedFileContent(relPath).await
if self.diffRevision > revision:
return

let stagedFileContent = vcs.getStagedFileContent(relPath).await
if self.diffRevision > revision:
return

let changes = vcs.getFileChanges(relPath, staged = true).await
if self.diffRevision > revision:
return

if self.diffDocument.isNil:
self.diffDocument = newTextDocument(self.configProvider,
Expand All @@ -1420,7 +1429,12 @@ proc updateDiffAsync*(self: TextDocumentEditor) {.async.} =

else:
let stagedFileContent = vcs.getStagedFileContent(relPath).await
if self.diffRevision > revision:
return

let changes = vcs.getFileChanges(relPath, staged = false).await
if self.diffRevision > revision:
return

if self.diffDocument.isNil:
self.diffDocument = newTextDocument(self.configProvider,
Expand All @@ -1430,10 +1444,15 @@ proc updateDiffAsync*(self: TextDocumentEditor) {.async.} =
self.diffChanges = changes
self.diffDocument.content = stagedFileContent

if gotoFirstDiff and self.diffChanges.getSome(changes) and changes.len > 0:
self.selection = (changes[0].target.first, 0).toSelection
self.updateTargetColumn(Last)
self.centerCursor(self.selection.last)

self.markDirty()

proc updateDiff*(self: TextDocumentEditor) {.expose("editor.text").} =
asyncCheck self.updateDiffAsync()
proc updateDiff*(self: TextDocumentEditor, gotoFirstDiff: bool = false) {.expose("editor.text").} =
asyncCheck self.updateDiffAsync(gotoFirstDiff)

proc checkoutFileAsync*(self: TextDocumentEditor) {.async.} =
if self.document.isNil:
Expand Down Expand Up @@ -3030,7 +3049,7 @@ proc handleTextDocumentLoaded(self: TextDocumentEditor) =
proc handleTextDocumentSaved(self: TextDocumentEditor) =
log lvlInfo, fmt"handleTextDocumentSaved '{self.document.filename}'"
if self.diffDocument.isNotNil:
asyncCheck self.updateDiffAsync()
asyncCheck self.updateDiffAsync(gotoFirstDiff=false)

proc handleCompletionsUpdated(self: TextDocumentEditor) =
self.completionsDirty = true
Expand Down
8 changes: 3 additions & 5 deletions src/ui/widget_builder_selector_popup.nim
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,8 @@ method createUI*(self: SelectorPopup, builder: UINodeBuilder, app: App): seq[pro
builder.panel(&{FillX, MaskContent, OverlappingChildren} + yFlag): #, userId = id):
let totalLineHeight = app.platform.totalLineHeight

const previewSize = 0.5

block:
builder.panel(&{FillX, LayoutVertical} + yFlag, w = bounds.w * (1 - previewSize)):
builder.panel(&{FillX, LayoutVertical} + yFlag, w = bounds.w * (1 - self.previewScale)):

builder.panel(&{FillX, SizeToContentY}):
result.add self.textEditor.createUI(builder, app)
Expand Down Expand Up @@ -146,8 +144,8 @@ method createUI*(self: SelectorPopup, builder: UINodeBuilder, app: App): seq[pro
x += maxWidths[col] + gap

if self.previewEditor.isNotNil:
builder.panel(0.UINodeFlags, x = bounds.w * (1 - previewSize),
w = bounds.w * previewSize, h = bounds.h):
builder.panel(0.UINodeFlags, x = bounds.w * (1 - self.previewScale),
w = bounds.w * self.previewScale, h = bounds.h):

self.previewEditor.active = self.focusPreview
result.add self.previewEditor.createUI(builder, app)
Expand Down
2 changes: 2 additions & 0 deletions src/vcs/vcs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type
VersionControlSystem* = ref object of RootObj
root*: string

func isUntracked*(fileInfo: VCSFileInfo): bool = fileInfo.unstagedStatus == Untracked

method getChangedFiles*(self: VersionControlSystem): Future[seq[VCSFileInfo]] {.base.} =
newSeq[VCSFileInfo]().toFuture

Expand Down

0 comments on commit 07736bc

Please sign in to comment.