Skip to content

Commit

Permalink
Implement diffing of real files
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick-Lucas committed Oct 13, 2021
1 parent 21f70fb commit 8aa31d1
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 87 deletions.
44 changes: 44 additions & 0 deletions packages/git/src/Git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
WatcherCallback,
WatcherEvent,
} from './types'
import { FileText } from 'src'

const PROFILING = true
let perfStart = (name: string) => {
Expand Down Expand Up @@ -561,6 +562,49 @@ export class Git {
}
}

getFilePlainText = async (
filePath: string | null,
sha: string | null = null,
): Promise<FileText | null> => {
const spawn = await this._getSpawn()
if (!spawn) {
return null
}

if (!filePath) {
return {
path: '',
type: '',
text: '',
}
}

const fileType = path.extname(filePath)

let plainText = null
if (sha) {
const cmd = ['show', `${sha}:${filePath}`]
plainText = await spawn(cmd)
} else {
const absoluteFilePath = path.join(this.cwd, filePath)
plainText = await new Promise<string>((resolve, reject) => {
fs.readFile(absoluteFilePath, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data.toString())
}
})
})
}

return {
path: filePath,
text: plainText,
type: fileType,
}
}

getDiffFromShas = async (
shaNew: string,
shaOld: string | null = null,
Expand Down
6 changes: 6 additions & 0 deletions packages/git/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,9 @@ export type GitFileOp =
| 'X' // Unknown
| 'B' // Broken
| undefined

export interface FileText {
path: string
text: string
type: string
}
80 changes: 57 additions & 23 deletions packages/giterm/app/renderer/components/diff/Diff.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,96 @@
import React from 'react'
import PropTypes from 'prop-types'
import React, { useCallback, useMemo, useRef } from 'react'
import styled from 'styled-components'

import { List } from 'app/lib/primitives'

import { DiffEditor } from 'app/lib/monaco'
import { DiffEditor, useMonaco } from 'app/lib/monaco'
import type { editor } from 'monaco-editor'

import { FilePatch } from './types'
import { FileText } from './useDiffData'
import { useValueEffect } from 'app/lib/hooks'

interface Props {
filePatch: FilePatch
left: FileText
right: FileText
}

export function Diff({ filePatch }: Props) {
const isRenamed =
filePatch.oldName !== filePatch.newName &&
!!filePatch.oldName &&
!!filePatch.newName
export function Diff({ left, right }: Props) {
const monaco = useMonaco()
const editorRef = useRef<editor.IStandaloneDiffEditor>()
const isRenamed = left.path !== right.path

const options: editor.IDiffEditorOptions = {
renderSideBySide: false,
renderOverviewRuler: false,
renderSideBySide: true,
readOnly: true,
}

const [leftLang, rightLang] = useMemo(() => {
if (!monaco) {
return ['', '']
}

const allLanguages = monaco.languages.getLanguages()

let leftLang = ''
let rightLang = ''
for (const language of allLanguages) {
if (leftLang && rightLang) {
break
}

const extensions = language.extensions ?? []

if (extensions.includes(left.type)) {
leftLang = language.id
}
if (extensions.includes(right.type)) {
rightLang = language.id
}
}

return [leftLang, rightLang]
}, [left.type, monaco, right.type])

useValueEffect(
left.path,
useCallback(() => {
if (editorRef.current) {
editorRef.current.revealPosition({ column: 0, lineNumber: 0 }, 1)
}
}, []),
)

return (
<DiffContainer>
<PatchName>
{isRenamed ? (
<>
<List.Label trimStart textAlign="right">
{filePatch.newName}
{left.path || '(Created)'}
</List.Label>
<PatchNameSeparator>{'->'}</PatchNameSeparator>
<List.Label trimStart>{filePatch.oldName}</List.Label>
<List.Label trimStart>{right.path || '(Deleted)'}</List.Label>
</>
) : (
<List.Label trimStart textAlign="center">
{filePatch.oldName ?? filePatch.selectedFileName}
{right.path}
</List.Label>
)}
</PatchName>

<DiffEditor
original={JSON.stringify(filePatch, null, 2)}
modified={JSON.stringify({ foo: filePatch }, null, 2)}
theme="vs-dark"
original={left.text}
modified={right.text}
theme="vs-dark" //TODO: custom giterm theme
options={options}
language="json"
originalLanguage={leftLang}
modifiedLanguage={rightLang}
onMount={(editor) => (editorRef.current = editor)}
/>
</DiffContainer>
)
}

Diff.propTypes = {
filePatch: PropTypes.object.isRequired,
}

const DiffContainer = styled.div`
display: flex;
flex-direction: column;
Expand Down
13 changes: 3 additions & 10 deletions packages/giterm/app/renderer/components/diff/DiffPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'

import { Panel } from 'app/lib/primitives'
Expand All @@ -10,9 +9,9 @@ import { Diff } from './Diff'
import { useDiffData } from './useDiffData'

export function DiffPanel() {
const { loading, filePath, setFilePath, filePatch, diff } = useDiffData()
const { loading, filePath, setFilePath, diff, left, right } = useDiffData()

if (loading || !diff || !filePatch || !filePath) {
if (loading || !diff || !left || !right) {
return (
<DiffContainer>
<MessageText>Loading</MessageText>
Expand All @@ -26,17 +25,11 @@ export function DiffPanel() {

<Files patches={diff.files} filePath={filePath} onChange={setFilePath} />

<Diff filePatch={filePatch} />
<Diff left={left} right={right} />
</StyledPanel>
)
}

DiffPanel.propTypes = {
mode: PropTypes.oneOf(['shas', 'index']),
shaNew: PropTypes.string,
shaOld: PropTypes.string,
}

const StyledPanel = styled(Panel)`
display: flex;
flex: 1 1 0;
Expand Down
2 changes: 2 additions & 0 deletions packages/giterm/app/renderer/components/diff/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ export type FilePatch = Modify<
{
selectedFileName: string
blocks: FilePatchBlock[]
originalText: string
modifiedText: string
}
>
107 changes: 53 additions & 54 deletions packages/giterm/app/renderer/components/diff/useDiffData.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { useMemo, useState, useEffect, useCallback } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { Git, DiffResult } from '@giterm/git'
import { Git, DiffResult, DiffFile, FileText } from '@giterm/git'

import { diffFileSelected } from 'app/store/diff/actions'

import { DiffLine, FilePatch } from './types'
export type { FileText }

export interface DiffData {
loading: boolean
filePath: string | null
filePath: string
setFilePath: (path: string) => void
diff: DiffResult | null
filePatch: FilePatch | null
left: FileText | null
right: FileText | null
}

export function useDiffData({ contextLines = 5 } = {}): DiffData {
Expand All @@ -32,6 +33,8 @@ export function useDiffData({ contextLines = 5 } = {}): DiffData {

const [loading, setLoading] = useState(true)
const [diff, setDiff] = useState<DiffResult | null>(null)
const [left, setLeft] = useState<FileText | null>(null)
const [right, setRight] = useState<FileText | null>(null)
useEffect(() => {
let cancelled = false

Expand All @@ -46,7 +49,10 @@ export function useDiffData({ contextLines = 5 } = {}): DiffData {
: await git.getDiffFromIndex({ contextLines })

if (!cancelled) {
diff != null && setDiff(diff)
if (diff) {
setDiff(diff)
}

setLoading(false)
}
}
Expand All @@ -58,73 +64,66 @@ export function useDiffData({ contextLines = 5 } = {}): DiffData {
}
}, [contextLines, cwd, mode, shaNew, shaOld])

const filePath = useMemo<string | null>(() => {
if (
!_filePath ||
!diff?.files?.some(
const fileDiff = useMemo<DiffFile | null>(() => {
if (_filePath) {
const fileDiff = diff?.files?.find(
(patch) => (patch.newName ?? patch.oldName) === _filePath,
)
) {
return diff?.files[0]?.newName ?? null
return fileDiff ?? null
} else {
return _filePath
return diff?.files[0] ?? null
}
}, [_filePath, diff?.files])

const filePatch = useMemo<FilePatch | null>(() => {
if (!diff || !filePath) {
return null
}
const file = diff.files.find(
(file) => file.oldName === filePath || file.newName === filePath,
)
if (!file) {
return null
useEffect(() => {
if (!fileDiff) {
return
}

const filePatch: FilePatch = {
...file,
selectedFileName: filePath,
blocks: [],
}
let cancelled = false

for (const block of file.blocks) {
const linesLeft: DiffLine[] = []
const linesRight: DiffLine[] = []

for (const line of block.lines) {
const isLeft = typeof line.oldNumber === 'number' && line.oldNumber >= 0
const isRight =
typeof line.newNumber === 'number' && line.newNumber >= 0

// Where line has not changed at-all we fix the row to the same index in both columns
if (isLeft && isRight) {
const headIndex = Math.max(linesLeft.length, linesRight.length)
linesLeft[headIndex] = line
linesRight[headIndex] = line
continue
}
const leftName = fileDiff.oldName
const rightName = fileDiff.newName

// Otherwise push one at a time to keep the diff compact
if (isLeft) {
linesLeft.push(line)
}
if (isRight) {
linesRight.push(line)
}
async function fetch() {
const git = new Git(cwd)

let left: FileText | null
let right: FileText | null
if (mode === 'shas') {
const shaOldRelative = shaOld ?? `${shaNew}~1`
left = await git.getFilePlainText(leftName, shaOldRelative)
right = await git.getFilePlainText(rightName, shaNew)
} else {
left = await git.getFilePlainText(leftName, 'HEAD')
right = await git.getFilePlainText(rightName)
}

filePatch.blocks.push({ ...block, linesLeft, linesRight })
if (!cancelled) {
if (left && right) {
setLeft(left)
setRight(right)
}

setLoading(false)
}
}

return filePatch
}, [diff, filePath])
fetch().catch((e) => {
throw e
})

return () => {
cancelled = true
}
}, [cwd, fileDiff, mode, shaNew, shaOld])

return {
loading,
filePath,
filePath: _filePath,
setFilePath,
diff,
filePatch,
left,
right,
}
}

0 comments on commit 8aa31d1

Please sign in to comment.