Add Reveal in Explorer option to changed file's context menu #1677
Conversation
items.push( | ||
{ type: 'separator' }, | ||
{ | ||
label: 'Open in external editor', |
shiftkey
May 22, 2017
Member
This needs to be platform-arized to Open in External Editor
for __DARWIN__
.
I'm also not sold on external editor
here for Windows, but that's because we didn't get around to implementing this in Classic Window - oops. @niik feels on what we should call this?
This needs to be platform-arized to Open in External Editor
for __DARWIN__
.
I'm also not sold on external editor
here for Windows, but that's because we didn't get around to implementing this in Classic Window - oops. @niik feels on what we should call this?
public openInExternalEditor(repository: Repository, filepath: string): boolean { | ||
const fullPath = Path.join(repository.path, filepath) | ||
// because Windows uses different path separators here | ||
const normalized = Path.normalize(fullPath) |
shiftkey
May 22, 2017
Member
Regarding this question:
should I refactor the path normalization code into a helper function? I just copied and pasted it from the repo clone code from the same file.
While it's rather trivial, I think extracting this to a local function would be 💎 .
I went with this, but you can totally try a different result if you find something better:
function normalizePath(repository: Repository, filepath: string): string {
const fullPath = Path.join(repository.path, filepath)
// because Windows uses different path separators here
const normalized = Path.normalize(fullPath)
return normalized
}
This then makes the usage:
const path = normalizePath(repository, filepath)
shell.openItem(path)
Regarding this question:
should I refactor the path normalization code into a helper function? I just copied and pasted it from the repo clone code from the same file.
While it's rather trivial, I think extracting this to a local function would be
I went with this, but you can totally try a different result if you find something better:
function normalizePath(repository: Repository, filepath: string): string {
const fullPath = Path.join(repository.path, filepath)
// because Windows uses different path separators here
const normalized = Path.normalize(fullPath)
return normalized
}
This then makes the usage:
const path = normalizePath(repository, filepath)
shell.openItem(path)
Pull request updated:
|
action: () => this.props.onOpenInExternalEditor(this.props.path), | ||
}, | ||
{ | ||
label: __DARWIN__ ? 'Reveal in Finder' : 'Reveal in Explorer', |
niik
May 22, 2017
Member
On Windows the expected terminology would be Show in Explorer
On Windows the expected terminology would be Show in Explorer
In the classic app on Windows we would always open a file with the 'Edit' verb to prevent the user from accidentally executing arbitrary code (like .exe, .js, .com etc). IIRC the default 'Open' action on Windows for .js is the windows script host which will happily run whatever you throw at it. If we couldn't find a registered Edit verb we still wouldn't launch with the 'Open' verb but rather fall back to opening the file in Explorer. I think we need to implement similar safeguards here although I'm not quite sure how to specify a verb in this environment. |
@niik Updated text per comment. For opening with a separate verb, I can't find any npm modules that can do this, so we may either need to write a module that wraps ShellExecute, or use node-ffi to wrap the function through ffi. |
Yeah, I think we should land this PR with just the 'Reveal in Finder/Show in Explorer' menu item and push the open option further down the line. It's tricky to get right and on macOS it seems like although there's an equivalent of the edit verb it can't be trusted to not execute code. I think as we start to get into 'Open in Atom/Open in [insert favorite IDE here]' the need for a generic open item will diminish somewhat. |
@zhuowei this looks like it's close, however there's a merge conflict that needs to be |
@shiftkey also #1677 (comment) |
@niik |
@niik Open button removed; only the Reveal in Finder/Open in Explorer menu item is left. Also, conflict resolved. |
function joinAndNormalizePath(repository: Repository, filepath: string): string { | ||
const fullPath = Path.join(repository.path, filepath) | ||
// because Windows uses different path separators here | ||
const normalized = Path.normalize(fullPath) |
niik
May 26, 2017
Member
I'm not sure I see why this is necessary? Some initial testing on my machine seems to suggest that Path.join
will normalize paths for us.
> Path.join('c:\\', 'users/markus olsson')
'c:\\users\\markus olsson'
> Path.join('c:\\', 'users/markus olsson/Documents')
'c:\\users\\markus olsson\\Documents'
> Path.join('c:\\users', 'niik/markus olsson/Documents')
'c:\\users\\niik\\markus olsson\\Documents'
> Path.join('c:\\users', 'niik/markus olsson/Documents\\GitHub')
'c:\\users\\niik\\markus olsson\\Documents\\GitHub'
> Path.join('c:\\users', 'niik/markus olsson/Documents\\GitHub/desktop')
'c:\\users\\niik\\markus olsson\\Documents\\GitHub\\desktop'
> Path.join('c:\\users', 'niik/../markus olsson/Documents\\GitHub/desktop')
'c:\\users\\markus olsson\\Documents\\GitHub\\desktop'
@shiftkey It seems you wrote this back in the day can you recall why this was necessary? If we can come up with a case where it is necessary I think we should add a test which highlights that scenario. If not I think we should ditch this function in favor of just joining the paths inline
I'm not sure I see why this is necessary? Some initial testing on my machine seems to suggest that Path.join
will normalize paths for us.
> Path.join('c:\\', 'users/markus olsson')
'c:\\users\\markus olsson'
> Path.join('c:\\', 'users/markus olsson/Documents')
'c:\\users\\markus olsson\\Documents'
> Path.join('c:\\users', 'niik/markus olsson/Documents')
'c:\\users\\niik\\markus olsson\\Documents'
> Path.join('c:\\users', 'niik/markus olsson/Documents\\GitHub')
'c:\\users\\niik\\markus olsson\\Documents\\GitHub'
> Path.join('c:\\users', 'niik/markus olsson/Documents\\GitHub/desktop')
'c:\\users\\niik\\markus olsson\\Documents\\GitHub\\desktop'
> Path.join('c:\\users', 'niik/../markus olsson/Documents\\GitHub/desktop')
'c:\\users\\markus olsson\\Documents\\GitHub\\desktop'
@shiftkey It seems you wrote this back in the day can you recall why this was necessary? If we can come up with a case where it is necessary I think we should add a test which highlights that scenario. If not I think we should ditch this function in favor of just joining the paths inline
shiftkey
May 26, 2017
Member
@niik gosh, I'm trying to recall but I really can't. Maybe this was something that came from the Node 6.x days that is no longer needed?
@niik gosh, I'm trying to recall but I really can't. Maybe this was something that came from the Node 6.x days that is no longer needed?
@@ -100,6 +101,15 @@ export class ChangedFile extends React.Component<IChangedFileProps, void> { | |||
}) | |||
} | |||
|
|||
if (this.props.status !== FileStatus.Deleted) { |
niik
May 26, 2017
Member
Could we still display this item but set it as disabled when the file is not around on disk?
Could we still display this item but set it as disabled when the file is not around on disk?
@@ -113,6 +113,10 @@ export class ChangesSidebar extends React.Component<IChangesSidebarProps, void> | |||
this.props.dispatcher.ignore(this.props.repository, pattern) | |||
} | |||
|
|||
private onRevealInFileManager = (path: string) => { |
niik
May 26, 2017
Member
Can we document this please, specifically with information about how the path parameter is expected to be a path relative to the repository root.
Can we document this please, specifically with information about how the path parameter is expected to be a path relative to the repository root.
@@ -26,6 +26,7 @@ interface IChangesListProps { | |||
readonly onCreateCommit: (message: ICommitMessage) => Promise<boolean> | |||
readonly onDiscardChanges: (file: WorkingDirectoryFileChange) => void | |||
readonly onDiscardAllChanges: (files: ReadonlyArray<WorkingDirectoryFileChange>) => void | |||
readonly onRevealInFileManager: (path: string) => void |
niik
May 26, 2017
Member
Can we document this please, specifically with information about how the path parameter is expected to be a path relative to the repository root.
Can we document this please, specifically with information about how the path parameter is expected to be a path relative to the repository root.
@@ -16,6 +16,7 @@ interface IChangedFileProps { | |||
readonly include: boolean | null | |||
readonly onIncludeChanged: (path: string, include: boolean) => void | |||
readonly onDiscardChanges: (path: string) => void | |||
readonly onRevealInFileManager: (path: string) => void |
niik
May 26, 2017
Member
Can we document this please, specifically with information about how the path parameter is expected to be a path relative to the repository root.
Can we document this please, specifically with information about how the path parameter is expected to be a path relative to the repository root.
@@ -841,6 +841,11 @@ export class Dispatcher { | |||
return this.appStore._setConfirmRepoRemoval(value) | |||
} | |||
|
|||
public revealInFileManager(repository: Repository, filepath: string): boolean { |
niik
May 26, 2017
Member
Can we document this please, specifically with information about how the path parameter is expected to be a path relative to the repository root. Could we also rename 'filepath' to either just 'path' or (preferably) 'relativeFilePath'
Can we document this please, specifically with information about how the path parameter is expected to be a path relative to the repository root. Could we also rename 'filepath' to either just 'path' or (preferably) 'relativeFilePath'
Addressed @niik's comments. |
This fixes parts of issue #1566.
Rebased to fix build. |
Nicely done @zhuowei, thank you! |
Fixes #1566.
I tested this on Windows 10; I don't have a Mac, but I think this would still work there since it just uses electron's shell api.
Questions: