밖에서 부르기: 명령줄 인자 수신 · 탐색기 우클릭 등록
git config 에 mergetool/difftool 로 등록하는 쪽은 전부터 있었는데 정작 그 인자를 받는 쪽이 없어 다리가 반만 놓여 있었다. 나머지 반쪽을 놓는다. - Services/StartupArgs: 켤 때 넘어온 인자를 읽는다. 이름표 붙은 것(--merge --base B --local L --remote R --merged M)과 그냥 넘어온 경로 둘 다 받는다. 탐색기는 "%1" 하나만 주므로 후자가 그 자리다. 경로가 폴더면 폴더 비교로, 파일이면 파일 비교로 보낸다. difftool 의 LOCAL/REMOTE 와 탐색기의 "먼저 고른 것/나중 것" 은 뜻이 달라 따로 담는다 - 이 도구의 왼쪽은 늘 "기준"이라 difftool 에서는 REMOTE 가 왼쪽이 된다. - Services/ShellIntegration: 탐색기 우클릭 메뉴를 파일·폴더·폴더 배경 세 자리에 넣는다. HKEY_CURRENT_USER 에만 쓰므로 관리자 권한이 필요 없고, 지울 때도 이 사람 것만 지운다 - 사내 PC 에 권한 없이 깔리는 것이 전제다. 셸 확장 DLL 을 만들지 않은 것도 같은 이유로, DLL 은 탐색기 프로세스 안에서 돌아 잘못되면 탐색기가 통째로 죽는다. 폴더 배경에서는 %1 이 아니라 %V 가 그 폴더다. - GitWorkspace.RouteStartupArgs 가 요청을 화면으로 보낸다. 창이 다 만들어진 뒤에 탭을 옮겨야 해서(그 전에는 옮길 곳이 없다) ApplicationIdle 로 미루고, 요청 자체는 남겨 두어 늦게 만들어진 화면이 스스로 집어 가게 했다(폴더 비교 쪽도 파일 비교와 같은 방식으로 추가). - 곁들여: 파일 비교가 "양쪽 경로가 모두 있을 때만" 열리고 있었다. 탐색기는 하나만 주므로 한쪽만 있어도 열도록 고쳤다 - 없는 쪽은 빈 내용이 되어 전부 추가로 보인다. ArgsTest 26건 신설. 전체 356건 통과. 실행 확인: 폴더 경로로 켜면 폴더 비교 탭에 그 폴더가 왼쪽에, 파일 두 개로 켜면 파일 비교 탭에 나란히. 등록 버튼을 누르면 레지스트리 3개 키가 생기고 버튼 이름이 "탐색기 우클릭 해제" 로 바뀐다. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
55d9deb418
commit
49a05a0628
23
doc/진행상황.md
23
doc/진행상황.md
@ -66,14 +66,30 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘
|
||||
|
||||
---
|
||||
|
||||
### ⑤ 밖에서 부르기 — **완료** (2026-09-08)
|
||||
|
||||
| 상태 | 항목 | 내용 | 확인 |
|
||||
|---|---|---|---|
|
||||
| **완료** | 명령줄 인자 수신 | `--merge/--diff --base/--local/--remote/--merged` 와 그냥 넘어온 경로. <b>이것이 다리의 나머지 반쪽</b>이다 - `git config` 에 등록하는 쪽은 전부터 있었는데 받는 쪽이 없었다 | `ArgsTest` 26건 · 실제 실행 2건 |
|
||||
| **완료** | 탐색기 우클릭 | 파일·폴더·폴더 배경 세 자리. Git 탭 버튼 하나로 등록/해제하고 버튼 이름이 지금 상태를 말한다 | 레지스트리 3개 키 확인 |
|
||||
|
||||
**HKEY_CURRENT_USER 에만 쓴다** - 관리자 권한이 필요 없고 지울 때도 이 사람 것만 지운다.
|
||||
사내 PC 에 권한 없이 깔리는 것이 전제라 이 선택이 중요하다. 셸 확장 DLL 을 만들지 않은 것도
|
||||
같은 이유다 - DLL 은 탐색기 프로세스 안에서 돌아 잘못되면 탐색기가 통째로 죽는다.
|
||||
|
||||
탐색기는 고른 것 <b>하나만</b> 준다. 그때는 왼쪽만 채우고 오른쪽은 비워 둔 채 화면을 연다 -
|
||||
사용자가 그 자리에서 상대를 고르면 되고, 아무것도 안 열리는 것보다 낫다.
|
||||
|
||||
---
|
||||
|
||||
## 3~4주차로 미룬 것
|
||||
|
||||
디테일이라 지금 붙잡을 일이 아니다.
|
||||
|
||||
- 파일·폴더 드래그&드롭, 최근 파일 목록
|
||||
- 로그인·권한
|
||||
- 탐색기 우클릭 등록, `git mergetool` 인자 수신
|
||||
(※ `git config` 에 등록하는 쪽은 이미 있는데 **받는 쪽이 없다** — 다리를 반만 놓은 상태다)
|
||||
- 탐색기에서 <b>두 개를 한 번에</b> 골라 비교(지금은 하나씩. 여러 개를 한 번에 받으려면
|
||||
레지스트리 MultiSelectModel 이나 셸 확장이 필요하다)
|
||||
- 비교 결과 보고서 내보내기
|
||||
- 대용량 파일 성능 (수만 줄) — 실제 소스 크기를 확인한 뒤 판단
|
||||
|
||||
@ -87,4 +103,5 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘
|
||||
| 2026-09-08 | **④ Git 조작 5가지** — amend · stash 목록 · 커밋 간 비교 · blame · 변경분 추출 | `9956567` |
|
||||
| 2026-09-08 | **③ 3-Way** — 자동 병합 · 충돌만 이동 · 병합 완료 판정 | `85c5e9e` |
|
||||
| 2026-09-08 | **① 폴더 비교** — 폴더째 복사·삭제 · 보기 필터 · 비교 취소 | `524a6a7` |
|
||||
| 2026-09-08 | **② 파일 비교** — 바꾸기 · 덩어리 반영 · 저장 형식 · 이진/그림 비교 | `de1a43c` + 아래 |
|
||||
| 2026-09-08 | **② 파일 비교** — 바꾸기 · 덩어리 반영 · 저장 형식 · 이진/그림 비교 | `de1a43c` `55d9deb` |
|
||||
| 2026-09-08 | **⑤ 밖에서 부르기** — 명령줄 인자 · 탐색기 우클릭 | 아래 |
|
||||
|
||||
@ -48,6 +48,11 @@ namespace MergeHub.Services
|
||||
// 그러면 주석에 등록한 이름 대신 윈도우 계정이 찍힌다.
|
||||
AuthorName = ResolveAuthorName();
|
||||
|
||||
// `git mergetool` · 탐색기 우클릭이 넘긴 것을 화면으로 보낸다.
|
||||
// 여기서 하는 이유 - 이 객체는 첫 화면이 만들어질 때 함께 만들어지므로
|
||||
// 요청을 남겨 둘 자리로 가장 이르다.
|
||||
RouteStartupArgs();
|
||||
|
||||
// 설정 화면에서 이름을 바꾸면 바로 따라간다.
|
||||
if (_currentUser is not null)
|
||||
{
|
||||
@ -379,6 +384,67 @@ namespace MergeHub.Services
|
||||
PlainCompareRequested.Invoke(pending.Left, pending.Right);
|
||||
}
|
||||
|
||||
// ================= 켤 때 넘어온 인자 =================
|
||||
|
||||
/// <summary>폴더 비교 화면이 아직 없을 때 남겨 두는 요청. 파일 비교 쪽과 같은 방식이다.</summary>
|
||||
private (string Left, string Right)? _pendingFolderCompare;
|
||||
|
||||
public void RequestFolderCompare(string left, string right)
|
||||
{
|
||||
_pendingFolderCompare = (left, right);
|
||||
OnUi(DeliverPendingFolderCompare);
|
||||
}
|
||||
|
||||
public void DeliverPendingFolderCompare()
|
||||
{
|
||||
if (_pendingFolderCompare is not { } pending) return;
|
||||
if (FolderCompareRequested is null) return;
|
||||
|
||||
_pendingFolderCompare = null;
|
||||
FolderCompareRequested.Invoke(pending.Left, pending.Right);
|
||||
}
|
||||
|
||||
public event Action<string, string>? FolderCompareRequested;
|
||||
|
||||
/// <summary>
|
||||
/// `git mergetool` · `git difftool` · 탐색기 우클릭이 넘긴 것을 화면으로 보낸다.
|
||||
///
|
||||
/// 창이 다 만들어진 뒤에 탭을 옮겨야 한다 - 그 전에는 옮길 곳이 아직 없어 요청이 사라진다.
|
||||
/// 요청 자체는 남겨 두므로(pending) 늦게 만들어진 화면도 스스로 집어 간다.
|
||||
/// </summary>
|
||||
private void RouteStartupArgs()
|
||||
{
|
||||
var args = StartupArgs.Current;
|
||||
|
||||
if (args.WantsFolder)
|
||||
{
|
||||
RequestFolderCompare(args.LeftFolder, args.RightFolder);
|
||||
ActivateWhenReady(MergeHub.ViewIds.Folder);
|
||||
AppendLog($"탐색기에서 폴더 비교 요청: {args.LeftFolder}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!args.WantsFiles) return;
|
||||
|
||||
var (left, right) = args.DiffPair;
|
||||
RequestPlainCompare(left, right);
|
||||
ActivateWhenReady(MergeHub.ViewIds.Home);
|
||||
|
||||
AppendLog(args.Mode == StartupMode.Merge
|
||||
? $"mergetool 로 호출됨: {args.Merged}"
|
||||
: $"파일 비교 요청: {left} ↔ {right}");
|
||||
}
|
||||
|
||||
private static void ActivateWhenReady(string viewId)
|
||||
{
|
||||
var app = System.Windows.Application.Current;
|
||||
if (app is null) return;
|
||||
|
||||
app.Dispatcher.BeginInvoke(
|
||||
new Action(() => UbisamBase.Core.Modules.TabManager.Activate(viewId)),
|
||||
System.Windows.Threading.DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
|
||||
/// <summary>ObservableCollection 변경은 UI 스레드에서만 허용된다.</summary>
|
||||
public void OnUi(Action action)
|
||||
{
|
||||
|
||||
105
src/MergeHub/Services/ShellIntegration.cs
Normal file
105
src/MergeHub/Services/ShellIntegration.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace MergeHub.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// 탐색기 우클릭 메뉴 등록.
|
||||
///
|
||||
/// <b>HKEY_CURRENT_USER 에만 쓴다</b> - 관리자 권한이 필요 없고, 지울 때도 이 사람 것만 지운다.
|
||||
/// 사내 PC 에 관리자 권한 없이 깔리는 것이 전제라 이 선택이 중요하다.
|
||||
///
|
||||
/// 셸 확장 DLL 을 만들지 않은 이유 - DLL 을 등록하면 탐색기 프로세스 안으로 들어가
|
||||
/// 잘못되면 탐색기가 통째로 죽는다. 레지스트리 항목만 쓰는 이 방식은 최악의 경우에도
|
||||
/// "메뉴를 눌러도 프로그램이 안 뜬다"에서 끝난다.
|
||||
/// </summary>
|
||||
public static class ShellIntegration
|
||||
{
|
||||
/// <summary>메뉴에 보일 이름.</summary>
|
||||
public const string MenuLabel = "MergeHub 로 비교";
|
||||
|
||||
private const string KeyName = "MergeHub.Compare";
|
||||
|
||||
/// <summary>파일 우클릭 · 폴더 우클릭 · 폴더 안 빈 곳 우클릭, 세 자리에 넣는다.</summary>
|
||||
private static readonly string[] Targets =
|
||||
{
|
||||
@"Software\Classes\*\shell\" + KeyName,
|
||||
@"Software\Classes\Directory\shell\" + KeyName,
|
||||
@"Software\Classes\Directory\Background\shell\" + KeyName,
|
||||
};
|
||||
|
||||
public static bool IsRegistered
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
using var key = Registry.CurrentUser.OpenSubKey(Targets[0]);
|
||||
return key is not null;
|
||||
}
|
||||
catch (System.Security.SecurityException)
|
||||
{
|
||||
return false; // 정책으로 막힌 PC 면 "안 걸려 있다"로 본다
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 우클릭 메뉴를 넣는다. 이미 있으면 경로만 새로 쓴다(프로그램을 옮겼을 때를 위해).
|
||||
/// </summary>
|
||||
public static void Register(string executablePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(executablePath)) throw new ArgumentException("실행 파일 경로가 비었습니다.");
|
||||
|
||||
var exe = executablePath;
|
||||
var icon = exe + ",0";
|
||||
|
||||
foreach (var path in Targets)
|
||||
{
|
||||
var background = path.Contains("Background");
|
||||
|
||||
using var key = Registry.CurrentUser.CreateSubKey(path)
|
||||
?? throw new InvalidOperationException($"레지스트리 키를 만들지 못했습니다: {path}");
|
||||
|
||||
key.SetValue(null, MenuLabel);
|
||||
key.SetValue("Icon", icon);
|
||||
|
||||
using var command = key.CreateSubKey("command")
|
||||
?? throw new InvalidOperationException("command 키를 만들지 못했습니다.");
|
||||
|
||||
// 폴더 안 빈 곳에서 부르면 %1 이 아니라 %V 가 그 폴더다.
|
||||
var target = background ? "%V" : "%1";
|
||||
command.SetValue(null, $"\"{exe}\" \"{target}\"");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unregister()
|
||||
{
|
||||
foreach (var path in Targets)
|
||||
{
|
||||
try
|
||||
{
|
||||
Registry.CurrentUser.DeleteSubKeyTree(path, throwOnMissingSubKey: false);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// 이미 없으면 그만이다.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>지금 돌고 있는 실행 파일. net48 에는 Environment.ProcessPath 가 없다.</summary>
|
||||
public static string CurrentExecutable()
|
||||
{
|
||||
var path = System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName;
|
||||
return string.IsNullOrEmpty(path)
|
||||
? Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UbisamBase.Shell.exe")
|
||||
: path!;
|
||||
}
|
||||
}
|
||||
}
|
||||
167
src/MergeHub/Services/StartupArgs.cs
Normal file
167
src/MergeHub/Services/StartupArgs.cs
Normal file
@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace MergeHub.Services
|
||||
{
|
||||
public enum StartupMode
|
||||
{
|
||||
/// <summary>그냥 켠 것. 지금까지처럼 설정의 기본 파일을 연다.</summary>
|
||||
None,
|
||||
|
||||
/// <summary>파일 두 개를 나란히(`git difftool`, 탐색기 우클릭).</summary>
|
||||
Diff,
|
||||
|
||||
/// <summary>충돌 해결(`git mergetool`). BASE·LOCAL·REMOTE 를 받고 MERGED 에 쓴다.</summary>
|
||||
Merge,
|
||||
|
||||
/// <summary>폴더 비교.</summary>
|
||||
Folder,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 프로그램을 켤 때 넘어온 인자.
|
||||
///
|
||||
/// 이것이 있어야 <b>`git mergetool` 과 탐색기 우클릭이 이 프로그램을 부를 수 있다.</b>
|
||||
/// (git config 에 등록하는 쪽은 전부터 있었는데 받는 쪽이 없어 다리가 반만 놓여 있었다.)
|
||||
///
|
||||
/// 받는 모양은 두 가지다.
|
||||
/// - 이름표 붙은 것: <c>--merge --base B --local L --remote R --merged M</c>
|
||||
/// - 그냥 경로: 탐색기가 <c>"%1"</c> 하나만 주는 경우. 파일이면 비교, 폴더면 폴더 비교로 본다.
|
||||
/// </summary>
|
||||
public sealed class StartupArgs
|
||||
{
|
||||
public StartupMode Mode { get; private set; } = StartupMode.None;
|
||||
|
||||
public string Base { get; private set; } = string.Empty;
|
||||
public string Local { get; private set; } = string.Empty;
|
||||
public string Remote { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>결과를 써 넣을 파일. `git mergetool` 이 이 파일을 다시 읽어 간다.</summary>
|
||||
public string Merged { get; private set; } = string.Empty;
|
||||
|
||||
public string LeftFolder { get; private set; } = string.Empty;
|
||||
public string RightFolder { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 이름표 없이 그냥 넘어온 파일 경로. 탐색기가 <c>"%1"</c> 만 줄 때가 이 자리다.
|
||||
/// 이름표가 붙은 <see cref="Local"/>·<see cref="Remote"/> 와 뜻이 달라 따로 둔다 -
|
||||
/// difftool 은 "내 것/상대 것"이지만 탐색기는 그냥 "먼저 고른 것/나중 것"이다.
|
||||
/// </summary>
|
||||
public string LooseLeft { get; private set; } = string.Empty;
|
||||
public string LooseRight { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>이번 실행에 넘어온 인자. 한 번만 읽는다.</summary>
|
||||
public static StartupArgs Current { get; } = Parse(SafeArgs());
|
||||
|
||||
private static IReadOnlyList<string> SafeArgs()
|
||||
{
|
||||
try
|
||||
{
|
||||
var all = Environment.GetCommandLineArgs();
|
||||
return all.Length > 1 ? new List<string>(all).GetRange(1, all.Length - 1) : Array.Empty<string>();
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public static StartupArgs Parse(IReadOnlyList<string> args)
|
||||
{
|
||||
var result = new StartupArgs();
|
||||
if (args is null || args.Count == 0) return result;
|
||||
|
||||
var loose = new List<string>();
|
||||
|
||||
for (var i = 0; i < args.Count; i++)
|
||||
{
|
||||
var arg = args[i] ?? string.Empty;
|
||||
|
||||
switch (arg.ToLowerInvariant())
|
||||
{
|
||||
case "--merge": result.Mode = StartupMode.Merge; continue;
|
||||
case "--diff": result.Mode = StartupMode.Diff; continue;
|
||||
case "--folder": result.Mode = StartupMode.Folder; continue;
|
||||
|
||||
case "--base": result.Base = Next(args, ref i); continue;
|
||||
case "--local": result.Local = Next(args, ref i); continue;
|
||||
case "--remote": result.Remote = Next(args, ref i); continue;
|
||||
case "--merged": result.Merged = Next(args, ref i); continue;
|
||||
}
|
||||
|
||||
// 이름표가 없는 것은 경로로 본다. 셸에서 넘어오는 "%1" 이 이 자리다.
|
||||
if (!arg.StartsWith("-", StringComparison.Ordinal) && arg.Length > 0) loose.Add(arg);
|
||||
}
|
||||
|
||||
Fill(result, loose);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string Next(IReadOnlyList<string> args, ref int i) =>
|
||||
i + 1 < args.Count ? args[++i] : string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 이름표 없이 넘어온 경로를 자리에 맞춘다.
|
||||
///
|
||||
/// 탐색기 우클릭은 고른 것 하나만 준다. 그때는 <b>왼쪽만 채우고 오른쪽은 비워 둔다</b> -
|
||||
/// 사용자가 그 자리에서 상대를 고르면 되고, 아무것도 안 열리는 것보다 훨씬 낫다.
|
||||
/// </summary>
|
||||
private static void Fill(StartupArgs result, List<string> loose)
|
||||
{
|
||||
if (loose.Count == 0) return;
|
||||
|
||||
var first = loose[0];
|
||||
var second = loose.Count > 1 ? loose[1] : string.Empty;
|
||||
|
||||
// 이름표로 이미 모드를 정했으면 남은 경로만 채운다.
|
||||
if (result.Mode == StartupMode.Folder || (result.Mode == StartupMode.None && IsFolder(first)))
|
||||
{
|
||||
result.Mode = StartupMode.Folder;
|
||||
if (result.LeftFolder.Length == 0) result.LeftFolder = first;
|
||||
if (result.RightFolder.Length == 0) result.RightFolder = second;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.Mode == StartupMode.None) result.Mode = StartupMode.Diff;
|
||||
|
||||
result.LooseLeft = first;
|
||||
result.LooseRight = second;
|
||||
}
|
||||
|
||||
private static bool IsFolder(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 파일 비교 화면에 넣을 두 파일.
|
||||
/// `git difftool` 은 LOCAL 이 내 것, REMOTE 가 상대 것이라 <b>왼쪽이 REMOTE</b> 가 된다 -
|
||||
/// 이 도구의 왼쪽은 늘 "기준"이기 때문이다.
|
||||
/// </summary>
|
||||
public (string Left, string Right) DiffPair
|
||||
{
|
||||
get
|
||||
{
|
||||
// 그냥 넘어온 경로가 있으면 그 순서가 곧 왼쪽·오른쪽이다.
|
||||
if (LooseLeft.Length > 0 || LooseRight.Length > 0) return (LooseLeft, LooseRight);
|
||||
|
||||
return Mode == StartupMode.Merge
|
||||
? (Base, Merged.Length > 0 ? Merged : Local)
|
||||
: (Remote.Length > 0 ? Remote : Base, Local);
|
||||
}
|
||||
}
|
||||
|
||||
public bool WantsFolder => Mode == StartupMode.Folder && LeftFolder.Length > 0;
|
||||
|
||||
public bool WantsFiles =>
|
||||
Mode is StartupMode.Diff or StartupMode.Merge && (DiffPair.Left.Length > 0 || DiffPair.Right.Length > 0);
|
||||
}
|
||||
}
|
||||
@ -111,6 +111,10 @@ namespace MergeHub.ViewModels
|
||||
{
|
||||
_workspace = workspace;
|
||||
|
||||
// 탐색기 우클릭으로 켰으면 그 폴더가 왼쪽에 들어온다.
|
||||
_workspace.FolderCompareRequested += OnFolderCompareRequested;
|
||||
_workspace.DeliverPendingFolderCompare();
|
||||
|
||||
// 저장소를 열어 뒀으면 왼쪽 기본값으로 삼는다 - 대개 그 폴더를 견주게 된다.
|
||||
if (!string.IsNullOrWhiteSpace(_workspace.RepositoryPath) &&
|
||||
Directory.Exists(_workspace.RepositoryPath))
|
||||
@ -119,6 +123,21 @@ namespace MergeHub.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 탐색기 우클릭·명령줄이 넘긴 폴더를 받는다.
|
||||
///
|
||||
/// 오른쪽이 비어 있으면 <b>왼쪽만 채우고 기다린다</b> - 탐색기는 고른 것 하나만 주므로
|
||||
/// 대개 이쪽이다. 아무 일도 안 일어나는 것보다 반쯤 채워 놓는 편이 낫다.
|
||||
/// </summary>
|
||||
private void OnFolderCompareRequested(string left, string right)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(left)) LeftFolder = left;
|
||||
if (!string.IsNullOrWhiteSpace(right)) RightFolder = right;
|
||||
|
||||
if (Directory.Exists(LeftFolder) && Directory.Exists(RightFolder)) _ = CompareAsync();
|
||||
else Summary = "오른쪽 폴더를 고르고 비교를 누르세요.";
|
||||
}
|
||||
|
||||
/// <summary>홈 탭에서 이 두 파일을 비교해 달라고 알린다.</summary>
|
||||
public event EventHandler<(string Left, string Right)>? OpenInDiffRequested;
|
||||
|
||||
|
||||
@ -528,6 +528,47 @@ namespace MergeHub.ViewModels
|
||||
_workspace.OnUi(() => MessageUtil.ShowSuccessToast("git config 에 mergehub 등록 완료"));
|
||||
});
|
||||
|
||||
// ================= 탐색기 우클릭 =================
|
||||
|
||||
/// <summary>지금 우클릭 메뉴에 걸려 있는지. 버튼 이름을 이 값으로 바꾼다.</summary>
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(ShellIntegrationLabel))]
|
||||
private bool _isShellRegistered = ShellIntegration.IsRegistered;
|
||||
|
||||
public string ShellIntegrationLabel =>
|
||||
IsShellRegistered ? "탐색기 우클릭 해제" : "탐색기 우클릭 등록";
|
||||
|
||||
/// <summary>
|
||||
/// 탐색기 우클릭 메뉴를 넣고 뺀다.
|
||||
///
|
||||
/// HKEY_CURRENT_USER 에만 쓰므로 <b>관리자 권한이 필요 없다</b> - 사내 PC 에 권한 없이
|
||||
/// 깔리는 것이 전제다. 셸 확장 DLL 을 만들지 않은 것도 같은 이유로,
|
||||
/// DLL 은 탐색기 프로세스 안에서 돌아 잘못되면 탐색기가 통째로 죽는다.
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
private void ToggleShellIntegration()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsShellRegistered)
|
||||
{
|
||||
ShellIntegration.Unregister();
|
||||
MessageUtil.ShowToast("탐색기 우클릭 메뉴를 뺐습니다");
|
||||
}
|
||||
else
|
||||
{
|
||||
ShellIntegration.Register(ShellIntegration.CurrentExecutable());
|
||||
MessageUtil.ShowSuccessToast($"파일·폴더를 우클릭하면 \"{ShellIntegration.MenuLabel}\" 가 보입니다");
|
||||
}
|
||||
|
||||
IsShellRegistered = ShellIntegration.IsRegistered;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageUtil.ShowErrorToast($"탐색기 등록 실패: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>CommunityToolkit 의 커맨드는 CommandManager 를 구독하지 않아 직접 알려야 한다.</summary>
|
||||
private void RaiseCanExecuteChanged()
|
||||
{
|
||||
|
||||
@ -1443,7 +1443,9 @@ namespace MergeHub.ViewModels
|
||||
/// <summary>고른 두 파일을 읽어 비교 화면을 만든다.</summary>
|
||||
private async Task LoadPlainFilesAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(LeftFile) || string.IsNullOrWhiteSpace(RightFile)) return;
|
||||
// 한쪽만 있어도 연다 - 탐색기 우클릭은 고른 파일 하나만 준다.
|
||||
// 없는 쪽은 빈 내용이 되어 전부 추가(또는 삭제)로 보인다.
|
||||
if (string.IsNullOrWhiteSpace(LeftFile) && string.IsNullOrWhiteSpace(RightFile)) return;
|
||||
|
||||
// 이진 파일은 줄 단위로 견줄 것이 없다. 깨진 글자로 화면을 채우느니 그렇다고 알린다.
|
||||
if (TextFile.IsBinary(LeftFile) || TextFile.IsBinary(RightFile))
|
||||
|
||||
@ -130,6 +130,11 @@
|
||||
ToolTip="진행 중인 merge / cherry-pick / revert / rebase 를 취소"/>
|
||||
<Button Content="mergetool 등록" Command="{Binding RegisterMergeToolCommand}" MinWidth="110"
|
||||
ToolTip="git config 의 merge.tool 로 이 프로그램을 등록 (전역 .gitconfig 수정)"/>
|
||||
|
||||
<!-- HKCU 에만 쓰므로 관리자 권한이 필요 없다. 버튼 이름이 지금 상태를 그대로 말한다. -->
|
||||
<Button Content="{Binding ShellIntegrationLabel}" Command="{Binding ToggleShellIntegrationCommand}"
|
||||
MinWidth="140"
|
||||
ToolTip="탐색기에서 파일·폴더를 우클릭하면 이 프로그램으로 열 수 있게 합니다 (관리자 권한 필요 없음)"/>
|
||||
</WrapPanel>
|
||||
|
||||
<!-- ===== 작업 영역 ===== -->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user