mirror of
https://github.com/desktop/desktop
synced 2024-09-18 07:32:01 +00:00
Merge branch 'development' into cherry-picking-onto-prs
This commit is contained in:
commit
8844b91ad8
|
@ -94,6 +94,52 @@ export function lineNumberForDiffLine(
|
|||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* For the given row in the diff, determine the range of elements that
|
||||
* should be displayed as interactive, as a hunk is not granular enough.
|
||||
* The values in the returned range are mapped to lines in the original diff,
|
||||
* in case the current diff has been partially expanded.
|
||||
*/
|
||||
export function findInteractiveOriginalDiffRange(
|
||||
hunks: ReadonlyArray<DiffHunk>,
|
||||
index: number
|
||||
): IDiffRange | null {
|
||||
const range = findInteractiveDiffRange(hunks, index)
|
||||
|
||||
if (range === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
const from = getLineInOriginalDiff(hunks, range.from)
|
||||
const to = getLineInOriginalDiff(hunks, range.to)
|
||||
|
||||
if (from === null || to === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
...range,
|
||||
from,
|
||||
to,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to get the line number in the original line from a given
|
||||
* line number in the current text diff (which might be expanded).
|
||||
*/
|
||||
export function getLineInOriginalDiff(
|
||||
hunks: ReadonlyArray<DiffHunk>,
|
||||
index: number
|
||||
) {
|
||||
const diffLine = diffLineForIndex(hunks, index)
|
||||
if (diffLine === null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return diffLine.originalLineNumber
|
||||
}
|
||||
|
||||
/**
|
||||
* For the given row in the diff, determine the range of elements that
|
||||
* should be displayed as interactive, as a hunk is not granular enough
|
||||
|
|
|
@ -28,7 +28,10 @@ import {
|
|||
} from 'react-virtualized'
|
||||
import { SideBySideDiffRow } from './side-by-side-diff-row'
|
||||
import memoize from 'memoize-one'
|
||||
import { findInteractiveDiffRange, DiffRangeType } from './diff-explorer'
|
||||
import {
|
||||
findInteractiveOriginalDiffRange,
|
||||
DiffRangeType,
|
||||
} from './diff-explorer'
|
||||
import {
|
||||
ChangedFile,
|
||||
DiffRow,
|
||||
|
@ -589,7 +592,7 @@ export class SideBySideDiff extends React.Component<
|
|||
const selection = this.getSelection()
|
||||
|
||||
if (selection !== undefined) {
|
||||
const range = findInteractiveDiffRange(diff.hunks, hunkStartLine)
|
||||
const range = findInteractiveOriginalDiffRange(diff.hunks, hunkStartLine)
|
||||
if (range !== null) {
|
||||
const { from, to } = range
|
||||
const sel = selection.withRangeSelection(from, to - from + 1, select)
|
||||
|
@ -629,7 +632,7 @@ export class SideBySideDiff extends React.Component<
|
|||
return
|
||||
}
|
||||
|
||||
const range = findInteractiveDiffRange(diff.hunks, diffLineNumber)
|
||||
const range = findInteractiveOriginalDiffRange(diff.hunks, diffLineNumber)
|
||||
if (range === null || range.type === null) {
|
||||
return
|
||||
}
|
||||
|
@ -656,7 +659,10 @@ export class SideBySideDiff extends React.Component<
|
|||
return
|
||||
}
|
||||
|
||||
const range = findInteractiveDiffRange(this.props.diff.hunks, hunkStartLine)
|
||||
const range = findInteractiveOriginalDiffRange(
|
||||
this.props.diff.hunks,
|
||||
hunkStartLine
|
||||
)
|
||||
if (range === null || range.type === null) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -21,10 +21,11 @@ import { DiffSyntaxMode, IDiffSyntaxModeSpec } from './diff-syntax-mode'
|
|||
import { CodeMirrorHost } from './code-mirror-host'
|
||||
import {
|
||||
diffLineForIndex,
|
||||
findInteractiveDiffRange,
|
||||
findInteractiveOriginalDiffRange,
|
||||
lineNumberForDiffLine,
|
||||
DiffRangeType,
|
||||
diffLineInfoForIndex,
|
||||
getLineInOriginalDiff,
|
||||
} from './diff-explorer'
|
||||
|
||||
import {
|
||||
|
@ -453,10 +454,15 @@ export class TextDiff extends React.Component<ITextDiffProps, ITextDiffState> {
|
|||
this.cancelSelection()
|
||||
}
|
||||
|
||||
const isSelected = !file.selection.isSelected(index)
|
||||
const indexInOriginalDiff = getLineInOriginalDiff(hunks, index)
|
||||
if (indexInOriginalDiff === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const isSelected = !file.selection.isSelected(indexInOriginalDiff)
|
||||
|
||||
if (kind === 'hunk') {
|
||||
const range = findInteractiveDiffRange(hunks, index)
|
||||
const range = findInteractiveOriginalDiffRange(hunks, index)
|
||||
if (!range) {
|
||||
console.error('unable to find range for given line in diff')
|
||||
return
|
||||
|
@ -465,7 +471,12 @@ export class TextDiff extends React.Component<ITextDiffProps, ITextDiffState> {
|
|||
const { from, to } = range
|
||||
this.selection = { isSelected, from, to, kind }
|
||||
} else if (kind === 'range') {
|
||||
this.selection = { isSelected, from: index, to: index, kind }
|
||||
this.selection = {
|
||||
isSelected,
|
||||
from: indexInOriginalDiff,
|
||||
to: indexInOriginalDiff,
|
||||
kind,
|
||||
}
|
||||
document.addEventListener('mousemove', this.onDocumentMouseMove)
|
||||
} else {
|
||||
assertNever(kind, `Unknown selection kind ${kind}`)
|
||||
|
@ -496,9 +507,15 @@ export class TextDiff extends React.Component<ITextDiffProps, ITextDiffState> {
|
|||
// pointer is placed underneath the last line so we clamp it
|
||||
// to the range of valid values.
|
||||
const max = Math.max(0, this.codeMirror.getDoc().lineCount() - 1)
|
||||
const to = clamp(this.codeMirror.lineAtHeight(ev.y), 0, max)
|
||||
const index = clamp(this.codeMirror.lineAtHeight(ev.y), 0, max)
|
||||
|
||||
this.codeMirror.scrollIntoView({ line: to, ch: 0 })
|
||||
this.codeMirror.scrollIntoView({ line: index, ch: 0 })
|
||||
|
||||
const to = getLineInOriginalDiff(this.state.diff.hunks, index)
|
||||
|
||||
if (to === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (to !== this.selection.to) {
|
||||
this.selection = { ...this.selection, to }
|
||||
|
@ -526,11 +543,21 @@ export class TextDiff extends React.Component<ITextDiffProps, ITextDiffState> {
|
|||
// we need to make sure the user is still within that hunk handle
|
||||
// section and in the correct range.
|
||||
if (this.selection.kind === 'hunk') {
|
||||
const index = this.codeMirror.lineAtHeight(ev.y)
|
||||
const indexInOriginalDiff = getLineInOriginalDiff(
|
||||
this.state.diff.hunks,
|
||||
index
|
||||
)
|
||||
if (indexInOriginalDiff === null) {
|
||||
return
|
||||
}
|
||||
|
||||
// Is the pointer over the same range (i.e hunk) that the
|
||||
// selection was originally started from?
|
||||
if (
|
||||
indexInOriginalDiff === null ||
|
||||
!targetHasClass(ev.target, 'hunk-handle') ||
|
||||
!inSelection(this.selection, this.codeMirror.lineAtHeight(ev.y))
|
||||
!inSelection(this.selection, indexInOriginalDiff)
|
||||
) {
|
||||
return this.cancelSelection()
|
||||
}
|
||||
|
@ -665,7 +692,10 @@ export class TextDiff extends React.Component<ITextDiffProps, ITextDiffState> {
|
|||
return null
|
||||
}
|
||||
|
||||
const range = findInteractiveDiffRange(this.state.diff.hunks, lineNumber)
|
||||
const range = findInteractiveOriginalDiffRange(
|
||||
this.state.diff.hunks,
|
||||
lineNumber
|
||||
)
|
||||
if (range === null) {
|
||||
return null
|
||||
}
|
||||
|
@ -1189,20 +1219,21 @@ export class TextDiff extends React.Component<ITextDiffProps, ITextDiffState> {
|
|||
return
|
||||
}
|
||||
const lineNumber = this.codeMirror.lineAtHeight(ev.y)
|
||||
|
||||
const diffLine = diffLineForIndex(this.state.diff.hunks, lineNumber)
|
||||
const hunks = this.state.diff.hunks
|
||||
const diffLine = diffLineForIndex(hunks, lineNumber)
|
||||
|
||||
if (!diffLine || !diffLine.isIncludeableLine()) {
|
||||
return
|
||||
}
|
||||
|
||||
const range = findInteractiveDiffRange(this.state.diff.hunks, lineNumber)
|
||||
const range = findInteractiveOriginalDiffRange(hunks, lineNumber)
|
||||
|
||||
if (range === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const { from, to } = range
|
||||
|
||||
this.hunkHighlightRange = { from, to, kind: 'hunk', isSelected: false }
|
||||
this.updateViewport()
|
||||
}
|
||||
|
|
|
@ -33,7 +33,8 @@ interface IDraggableProps {
|
|||
}
|
||||
|
||||
export class Draggable extends React.Component<IDraggableProps> {
|
||||
private dragStarted: boolean = false
|
||||
private hasDragStarted: boolean = false
|
||||
private hasDragEnded: boolean = false
|
||||
private dragElement: HTMLElement | null = null
|
||||
private elemBelow: Element | null = null
|
||||
// Default offset to place the cursor slightly above the top left corner of
|
||||
|
@ -41,7 +42,6 @@ export class Draggable extends React.Component<IDraggableProps> {
|
|||
// dragElement then elemBelow will always return the dragElement and cannot
|
||||
// detect drop targets or scroll elements.
|
||||
private verticalOffset: number = __DARWIN__ ? 32 : 15
|
||||
private hasMouseUpOccurred = false
|
||||
|
||||
public componentDidMount() {
|
||||
this.dragElement = document.getElementById('dragElement')
|
||||
|
@ -58,7 +58,7 @@ export class Draggable extends React.Component<IDraggableProps> {
|
|||
}
|
||||
|
||||
private initializeDrag(): void {
|
||||
this.dragStarted = false
|
||||
this.hasDragStarted = false
|
||||
this.elemBelow = null
|
||||
}
|
||||
|
||||
|
@ -72,10 +72,10 @@ export class Draggable extends React.Component<IDraggableProps> {
|
|||
if (!this.canDragCommit(event)) {
|
||||
return
|
||||
}
|
||||
this.hasMouseUpOccurred = false
|
||||
document.onmouseup = this.onMouseUp
|
||||
this.hasDragEnded = false
|
||||
document.onmouseup = this.handleDragEndEvent
|
||||
await sleep(100)
|
||||
if (this.hasMouseUpOccurred) {
|
||||
if (this.hasDragEnded) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -91,16 +91,17 @@ export class Draggable extends React.Component<IDraggableProps> {
|
|||
* just clicking a draggable element.
|
||||
*/
|
||||
private onMouseMove = (moveEvent: MouseEvent) => {
|
||||
if (this.hasMouseUpOccurred) {
|
||||
if (this.hasDragEnded) {
|
||||
this.onDragEnd()
|
||||
return
|
||||
}
|
||||
// start drag
|
||||
if (!this.dragStarted) {
|
||||
if (!this.hasDragStarted) {
|
||||
this.props.onRenderDragElement()
|
||||
this.props.onDragStart()
|
||||
dragAndDropManager.dragStarted()
|
||||
this.dragStarted = true
|
||||
this.hasDragStarted = true
|
||||
window.addEventListener('keyup', this.onKeyUp)
|
||||
}
|
||||
|
||||
// move drag element where mouse is
|
||||
|
@ -126,12 +127,20 @@ export class Draggable extends React.Component<IDraggableProps> {
|
|||
/**
|
||||
* End a drag event
|
||||
*/
|
||||
private onMouseUp = () => {
|
||||
this.hasMouseUpOccurred = true
|
||||
if (this.dragStarted) {
|
||||
private handleDragEndEvent = () => {
|
||||
this.hasDragEnded = true
|
||||
if (this.hasDragStarted) {
|
||||
this.onDragEnd()
|
||||
}
|
||||
document.onmouseup = null
|
||||
window.removeEventListener('keyup', this.onKeyUp)
|
||||
}
|
||||
|
||||
private onKeyUp = (event: KeyboardEvent) => {
|
||||
if (event.key !== 'Escape') {
|
||||
return
|
||||
}
|
||||
this.handleDragEndEvent()
|
||||
}
|
||||
|
||||
private onDragEnd(): void {
|
||||
|
|
|
@ -118,9 +118,60 @@ function mergeDeferredContextMenuItems(
|
|||
})
|
||||
}
|
||||
|
||||
if (!__DARWIN__) {
|
||||
// NOTE: "On macOS as we use the native APIs there is no way to set the
|
||||
// language that the spellchecker uses" -- electron docs Therefore, we are
|
||||
// only allowing setting to English for non-mac machines.
|
||||
const spellCheckLanguageItem = getSpellCheckLanguageMenuItem(
|
||||
webContents.session
|
||||
)
|
||||
if (spellCheckLanguageItem !== null) {
|
||||
items.push(spellCheckLanguageItem)
|
||||
}
|
||||
}
|
||||
|
||||
showContextualMenu(items, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get a menu item to give user the option to use English or their
|
||||
* system language.
|
||||
*
|
||||
* If system language is english, it returns null. If spellchecker is not set to
|
||||
* english, it returns item that can set it to English. If spellchecker is set
|
||||
* to english, it returns the item that can set it to their system language.
|
||||
*/
|
||||
function getSpellCheckLanguageMenuItem(
|
||||
session: Electron.session
|
||||
): IMenuItem | null {
|
||||
const userLanguageCode = remote.app.getLocale()
|
||||
const englishLanguageCode = 'en-US'
|
||||
const spellcheckLanguageCodes = session.getSpellCheckerLanguages()
|
||||
|
||||
if (
|
||||
userLanguageCode === englishLanguageCode &&
|
||||
spellcheckLanguageCodes.includes(englishLanguageCode)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const languageCode =
|
||||
spellcheckLanguageCodes.includes(englishLanguageCode) &&
|
||||
!spellcheckLanguageCodes.includes(userLanguageCode)
|
||||
? userLanguageCode
|
||||
: englishLanguageCode
|
||||
|
||||
const label =
|
||||
languageCode === englishLanguageCode
|
||||
? 'Set spellcheck to English'
|
||||
: 'Set spellcheck to system language'
|
||||
|
||||
return {
|
||||
label,
|
||||
action: () => session.setSpellCheckerLanguages([languageCode]),
|
||||
}
|
||||
}
|
||||
|
||||
/** Show the given menu items in a contextual menu. */
|
||||
export async function showContextualMenu(
|
||||
items: ReadonlyArray<IMenuItem>,
|
||||
|
|
Loading…
Reference in a new issue