Merge pull request #18157 from desktop/add-selectable-range-info

Add Diff Selectable Line Group Check All: Add selectable range info
This commit is contained in:
tidy-dev 2024-02-14 06:56:10 -05:00 committed by GitHub
commit 0a20e1fa2b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 146 additions and 4 deletions

View file

@ -135,6 +135,54 @@ export class DiffSelection {
}
}
/**
* Returns a value indicating whether the range is all selected, partially
* selected, or not selected.
*
* @param from The line index (inclusive) from where to checking the range.
*
* @param length The number of lines to check from the start point of
* 'from', Assumes positive number, returns None if length is <= 0.
*/
public isRangeSelected(from: number, length: number): DiffSelectionType {
if (length <= 0) {
// This shouldn't happen? But if it does we'll log it and return None.
return DiffSelectionType.None
}
const computedSelectionType = this.getSelectionType()
if (computedSelectionType !== DiffSelectionType.Partial) {
// Nothing for us to do here. If all lines are selected or none, then any
// range of lines will be the same.
return computedSelectionType
}
if (length === 1) {
return this.isSelected(from)
? DiffSelectionType.All
: DiffSelectionType.None
}
const to = from + length
let foundSelected = false
let foundDeselected = false
for (let i = from; i < to; i++) {
if (this.isSelected(i)) {
foundSelected = true
}
if (!this.isSelected(i)) {
foundDeselected = true
}
if (foundSelected && foundDeselected) {
return DiffSelectionType.Partial
}
}
return foundSelected ? DiffSelectionType.All : DiffSelectionType.None
}
/**
* Returns a value indicating wether the given line number is selectable.
* A line not being selectable usually means it's a hunk header or a context

View file

@ -13,7 +13,7 @@ import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { narrowNoNewlineSymbol } from './text-diff'
import { shallowEquals, structuralEquals } from '../../lib/equality'
import { DiffHunkExpansionType } from '../../models/diff'
import { DiffHunkExpansionType, DiffSelectionType } from '../../models/diff'
import { PopoverAnchorPosition } from '../lib/popover'
import { WhitespaceHintPopover } from './whitespace-hint-popover'
import { TooltipDirection } from '../lib/tooltip'
@ -27,6 +27,37 @@ enum DiffRowPrefix {
Nothing = '\u{A0}',
}
/**
* This interface is used to pass information about a continuous group of
* selectable rows to the row component as it pertains to a given row.
*
* Primarily used for the styling of the row and it's check all control.
*/
export interface IRowSelectableGroup {
/**
* Whether or not the row is the first in the selectable group
*/
isFirst: boolean
/**
* Whether or not the row is the last in the selectable group
*/
isLast: boolean
/**
* Whether or not the group is hovered by the mouse
*/
isGroupHovered: boolean
/**
* Whether or not the group is focused by the keyboard
*/
isGroupFocused: boolean
/**
* The selection state of the group - 'All', 'Partial', or 'None'
*/
groupSelectionState: DiffSelectionType | null
}
interface ISideBySideDiffRowProps {
/**
* The row data. This contains most of the information used to render the row.

View file

@ -29,7 +29,7 @@ import {
OverscanIndicesGetterParams,
defaultOverscanIndicesGetter,
} from 'react-virtualized'
import { SideBySideDiffRow } from './side-by-side-diff-row'
import { IRowSelectableGroup, SideBySideDiffRow } from './side-by-side-diff-row'
import memoize from 'memoize-one'
import {
findInteractiveOriginalDiffRange,
@ -609,6 +609,63 @@ export class SideBySideDiff extends React.Component<
return defaultOverscanIndicesGetter({ ...params, startIndex, stopIndex })
}
/**
* Gathers information about if the row is in a selectable group. This
* information is used to facilitate the use of check all feature for the
* selectable group.
*
* This will return null if the row is not in a selectable group. A group is
* more than one row.
*/
private getRowSelectableGroupDetails(
row: SimplifiedDiffRow,
prev: SimplifiedDiffRow,
next: SimplifiedDiffRow
): IRowSelectableGroup | null {
if (!('hunkStartLine' in row)) {
// can't be a selection hunk without a hunkStartLine
return null
}
const { diff, hoveredHunk } = this.state
const selectableType = [
DiffRowType.Added,
DiffRowType.Deleted,
DiffRowType.Modified,
]
if (!selectableType.includes(row.type)) {
// We only care about selectable rows
return null
}
const range = findInteractiveOriginalDiffRange(
diff.hunks,
row.hunkStartLine
)
if (range === null || range.to - range.from === 0) {
// We only care about ranges with more than one line
return null
}
const selection = this.getSelection()
if (selection === undefined) {
// We only care about selectable rows.. so if no selection, no selectable rows
return null
}
const { from, to } = range
return {
isFirst: prev === undefined || !selectableType.includes(prev.type),
isLast: next === undefined || !selectableType.includes(next.type),
isGroupHovered: hoveredHunk === row.hunkStartLine,
isGroupFocused: true, // focusedHunk === row.hunkStartLine, - To be added in later PR
groupSelectionState: selection.isRangeSelected(from, to - from + 1),
}
}
private renderRow = ({ index, parent, style, key }: ListRowProps) => {
const { diff } = this.state
const rows = getDiffRows(
@ -644,8 +701,14 @@ export class SideBySideDiff extends React.Component<
const rowWithTokens = this.createFullRow(row, index)
const isHunkHovered =
'hunkStartLine' in row && this.state.hoveredHunk === row.hunkStartLine
const getRowSelectableGroupDetails = this.getRowSelectableGroupDetails(
row,
prev,
next
)
// Just temporary until pass the whole row group data down.
const isHunkHovered = !!getRowSelectableGroupDetails?.isGroupHovered
return (
<CellMeasurer