파일 비교: 바꾸기(Ctrl+H) · 덩어리 단위 반영 · 저장 형식 고르기
- 바꾸기: 찾기 칸 아래에 바꿀 말 줄이 붙는다. Ctrl+F 로 열면 안 뜬다 - 찾기만 할 때 자리를 뺏지 않기 위해서다. "바꾸기" 는 고른 자리가 찾는 말일 때만 바꾸고 다음으로 넘어간다 - 엉뚱한 자리를 덮어쓰는 것보다 한 번 더 누르게 하는 편이 낫다. "모두" 는 한 번에 바꾸고 몇 개인지 알려 준다(되돌리기는 편집기의 Ctrl+Z 가 맡는다). - 덩어리 단위 반영: 줄 하나씩만 되면 연속 다섯 줄을 다섯 번 눌러야 한다. WinMerge·Beyond Compare 가 덩어리를 기본 단위로 쓰는 이유다. 덩어리는 "차이로 잡힌 줄이 이어지는 구간"이고, 캐럿이 그 안에 있을 때만 켜진다. Alt+Shift+→. - 저장 형식: 사내 소스에 CP949 로 저장된 파일이 섞여 있어 새로 만드는 파일과 형식이 갈린다. 읽어서 보여 주기만 하면 그 갈림을 못 고치므로 저장할 때 고를 수 있게 했다. 기본값 "원본 유지" 라 지금까지와 동작이 같다. Save & Commit 경로도 같은 값을 쓴다 (전에는 File.WriteAllText 로 UTF-8 고정이었다). 실행 확인: PulseUs → PulseMicros 2개 바꿈 · 마지막 차이에서 2줄 한 번에 반영. 단축키 61 → 63건, 전체 305건 통과. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
524a6a715d
commit
de1a43cc70
14
doc/진행상황.md
14
doc/진행상황.md
@ -55,14 +55,14 @@ CommunityToolkit 커맨드는 CommandManager 를 구독하지 않는데 이 둘
|
||||
되돌릴 수 없는 일이라 <b>화면 없이 검사할 수 있어야</b> 하기 때문이다 - 겹치는 파일은 덮고,
|
||||
대상에만 있던 파일은 남기고, 두 번 복사해도 같은지를 테스트로 못 박았다.
|
||||
|
||||
### ② 파일 비교
|
||||
### ② 파일 비교 (2026-09-08)
|
||||
|
||||
| 상태 | 항목 | 내용 |
|
||||
|---|---|---|
|
||||
| 대기 | 바꾸기 (Ctrl+H) | 찾기만 있다 |
|
||||
| 대기 | 덩어리 단위 반영 | 줄 단위 아니면 전체뿐. 연속 5줄이면 5번 누른다 |
|
||||
| 대기 | 인코딩·줄바꿈 변환 저장 | CP949 ↔ UTF-8. 지금은 읽어서 보여주기만 |
|
||||
| 대기 | 바이너리·이미지 비교 | 감지해서 안내만 하고 끝난다 |
|
||||
| 상태 | 항목 | 내용 | 확인 |
|
||||
|---|---|---|---|
|
||||
| **완료** | 바꾸기 (Ctrl+H) | 찾기 칸 아래에 바꿀 말 줄이 붙는다(Ctrl+F 로 열면 안 뜬다 - 찾기만 할 때 자리를 뺏지 않는다). `바꾸기` 는 고른 자리가 <b>찾는 말일 때만</b> 바꾸고 다음으로 넘어간다 - 엉뚱한 자리를 덮어쓰는 것보다 한 번 더 누르게 하는 편이 낫다 | `PulseUs → PulseMicros` 2개 바꿈 |
|
||||
| **완료** | 덩어리 단위 반영 | `이 덩어리 반영`(`Alt+Shift+→`). 차이로 잡힌 줄이 이어지는 구간을 한 번에 | 마지막 차이에서 2줄 한 번에 반영 |
|
||||
| **완료** | 인코딩·줄바꿈 변환 저장 | ACTIONS 의 `저장 형식` 두 칸. 기본 "원본 유지"라 지금까지와 똑같이 나간다. UTF-8 / UTF-8(BOM) / CP949 · CRLF / LF | 화면 확인 |
|
||||
| 대기 | 바이너리·이미지 비교 | 감지해서 안내만 하고 끝난다 | |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@ -21,11 +21,13 @@ namespace MergeHub.Services
|
||||
GoToLine,
|
||||
NextBookmark,
|
||||
ToggleBookmark,
|
||||
AcceptBlock,
|
||||
NextConflict,
|
||||
PrevConflict,
|
||||
AutoMerge,
|
||||
OpenFolderCompare,
|
||||
Find,
|
||||
Replace,
|
||||
FindNext,
|
||||
FindPrev,
|
||||
CloseFind,
|
||||
@ -118,6 +120,9 @@ namespace MergeHub.Services
|
||||
"이전 충돌 (3-Way)"),
|
||||
new ShortcutEntry(ShortcutAction.AutoMerge, Key.M, ModifierKeys.Alt, "병합·편집",
|
||||
"자동 병합 - 저쪽만 고친 줄을 한 번에 가져온다 (3-Way)"),
|
||||
new ShortcutEntry(ShortcutAction.AcceptBlock, Key.Right, ModifierKeys.Alt | ModifierKeys.Shift, "병합·편집",
|
||||
"이 덩어리 반영 - 차이가 이어지는 구간을 한 번에",
|
||||
"줄 하나씩만 되면 연속 다섯 줄을 다섯 번 눌러야 한다."),
|
||||
|
||||
new ShortcutEntry(ShortcutAction.Undo, Key.Z, ModifierKeys.Control, "병합·편집", "실행 취소",
|
||||
"머지 반영을 되돌린다. 되돌릴 머지 작업이 없으면 편집기의 글자 되돌리기가 그대로 동작한다."),
|
||||
@ -135,6 +140,7 @@ namespace MergeHub.Services
|
||||
new ShortcutEntry(ShortcutAction.Save, Key.S, ModifierKeys.Control, "파일·보기", "저장"),
|
||||
|
||||
new ShortcutEntry(ShortcutAction.Find, Key.F, ModifierKeys.Control, "찾기", "텍스트 찾기"),
|
||||
new ShortcutEntry(ShortcutAction.Replace, Key.H, ModifierKeys.Control, "찾기", "바꾸기 (찾기 칸이 열리며 바꿀 말 칸이 함께 뜬다)"),
|
||||
new ShortcutEntry(ShortcutAction.FindNext, Key.F3, ModifierKeys.None, "찾기", "다음 찾기"),
|
||||
new ShortcutEntry(ShortcutAction.FindPrev, Key.F3, ModifierKeys.Shift, "찾기", "이전 찾기"),
|
||||
new ShortcutEntry(ShortcutAction.CloseFind, Key.Escape, ModifierKeys.None, "찾기", "찾기 칸 닫기"),
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@ -52,24 +53,70 @@ namespace MergeHub.Services
|
||||
return encoding.GetString(bytes, offset, bytes.Length - offset);
|
||||
}
|
||||
|
||||
/// <summary>저장할 때 고를 수 있는 인코딩. 빈 값(원본 유지)이 기본이다.</summary>
|
||||
public const string KeepOriginal = "원본 유지";
|
||||
|
||||
public static IReadOnlyList<string> SaveEncodings { get; } =
|
||||
new[] { KeepOriginal, "UTF-8", "UTF-8 (BOM)", "CP949" };
|
||||
|
||||
public static IReadOnlyList<string> SaveNewLines { get; } =
|
||||
new[] { KeepOriginal, "CRLF", "LF" };
|
||||
|
||||
/// <summary>
|
||||
/// 원래 파일의 인코딩과 줄바꿈을 그대로 유지해 덮어쓴다.
|
||||
/// 새 파일이면 UTF-8(BOM 없음) · CRLF 로 쓴다.
|
||||
/// </summary>
|
||||
public static void Write(string path, string content)
|
||||
public static void Write(string path, string content) => Write(path, content, null, null);
|
||||
|
||||
/// <summary>
|
||||
/// 인코딩·줄바꿈을 <b>바꿔서</b> 저장한다. 둘 다 <c>null</c> 이나 "원본 유지"면 지금까지와 같다.
|
||||
///
|
||||
/// 사내 소스에는 CP949 로 저장된 파일이 섞여 있어, 새로 만드는 파일과 형식이 갈린다.
|
||||
/// 읽어서 보여 주기만 하면 그 갈림을 못 고치므로 저장할 때 고를 수 있게 한다.
|
||||
/// </summary>
|
||||
public static void Write(string path, string content, string? encodingName, string? newLineName)
|
||||
{
|
||||
var encoding = File.Exists(path)
|
||||
? Detect(File.ReadAllBytes(path))
|
||||
: new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||
var encoding = ResolveEncoding(path, encodingName);
|
||||
var newLine = ResolveNewLine(path, newLineName);
|
||||
|
||||
var newLine = File.Exists(path) ? Describe(path).NewLine : "CRLF";
|
||||
var text = content.Replace("\r\n", "\n");
|
||||
|
||||
if (newLine != "LF") text = text.Replace("\n", "\r\n");
|
||||
|
||||
File.WriteAllText(path, text, encoding);
|
||||
}
|
||||
|
||||
private static Encoding ResolveEncoding(string path, string? name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case "UTF-8": return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||
case "UTF-8 (BOM)": return new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
|
||||
case "CP949": return GetCp949();
|
||||
}
|
||||
|
||||
return File.Exists(path)
|
||||
? Detect(File.ReadAllBytes(path))
|
||||
: new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||
}
|
||||
|
||||
private static Encoding GetCp949()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Encoding.GetEncoding(949);
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
return Ansi; // 코드페이지가 없는 장비면 시스템 기본으로 물러난다
|
||||
}
|
||||
}
|
||||
|
||||
private static string ResolveNewLine(string path, string? name)
|
||||
{
|
||||
if (name == "CRLF" || name == "LF") return name;
|
||||
return File.Exists(path) ? Describe(path).NewLine : "CRLF";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 글이 아니라 이진 파일인지. 앞부분에 NUL 바이트가 있으면 이진으로 본다 -
|
||||
/// Git 을 비롯한 대부분의 도구가 쓰는 판별이고, 실제 텍스트에는 NUL 이 들어가지 않는다.
|
||||
|
||||
@ -406,6 +406,8 @@ namespace MergeHub.ViewModels
|
||||
|
||||
var content = BuildLocalText();
|
||||
var message = CommitMessage;
|
||||
var encoding = SaveEncoding;
|
||||
var newLine = SaveNewLine;
|
||||
string? sha = null;
|
||||
|
||||
var ok = await _workspace.RunAsync("Save & Commit", () =>
|
||||
@ -420,7 +422,9 @@ namespace MergeHub.ViewModels
|
||||
{
|
||||
var full = Path.Combine(_workspace.RepositoryPath,
|
||||
path!.Replace('/', Path.DirectorySeparatorChar));
|
||||
File.WriteAllText(full, content);
|
||||
|
||||
// 저장 형식을 고른 값으로 쓴다("원본 유지"면 지금까지와 같다).
|
||||
TextFile.Write(full, content, encoding, newLine);
|
||||
_workspace.Git.Stage(path!);
|
||||
}
|
||||
|
||||
@ -469,6 +473,7 @@ namespace MergeHub.ViewModels
|
||||
SavePlainCommand.NotifyCanExecuteChanged();
|
||||
|
||||
AutoMergeCommand.NotifyCanExecuteChanged();
|
||||
AcceptBlockCommand.NotifyCanExecuteChanged();
|
||||
NextConflictCommand.NotifyCanExecuteChanged();
|
||||
PrevConflictCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
@ -1520,7 +1525,11 @@ namespace MergeHub.ViewModels
|
||||
var content = BuildLocalText();
|
||||
var target = RightFile;
|
||||
|
||||
var ok = await _workspace.RunAsync("파일 저장", () => TextFile.Write(target, content));
|
||||
var encoding = SaveEncoding;
|
||||
var newLine = SaveNewLine;
|
||||
|
||||
var ok = await _workspace.RunAsync("파일 저장",
|
||||
() => TextFile.Write(target, content, encoding, newLine));
|
||||
if (!ok) return;
|
||||
|
||||
_workspace.OnUi(() =>
|
||||
@ -1694,6 +1703,9 @@ namespace MergeHub.ViewModels
|
||||
/// <summary>지금 캐럿이 놓인 행. 편집기 줄 번호와 1:1 이다.</summary>
|
||||
[ObservableProperty] private int _caretRow;
|
||||
|
||||
/// <summary>줄을 옮기면 "이 덩어리 반영"이 켜지고 꺼진다.</summary>
|
||||
partial void OnCaretRowChanged(int value) => AcceptBlockCommand.NotifyCanExecuteChanged();
|
||||
|
||||
/// <summary>상태바에 띄우는 "차이 3 / 15".</summary>
|
||||
[ObservableProperty] private string _diffPosition = "차이 -";
|
||||
|
||||
@ -2023,6 +2035,57 @@ namespace MergeHub.ViewModels
|
||||
RefreshProblems();
|
||||
}
|
||||
|
||||
// ================= 저장 형식 · 덩어리 반영 =================
|
||||
|
||||
/// <summary>
|
||||
/// 저장할 때 쓸 인코딩·줄바꿈. 기본은 "원본 유지"라 지금까지와 똑같이 동작한다.
|
||||
///
|
||||
/// 사내 소스에는 CP949 로 저장된 파일이 섞여 있어 새로 만드는 파일과 형식이 갈린다.
|
||||
/// 읽어서 보여 주기만 하면 그 갈림을 못 고치므로 저장할 때 고를 수 있게 한다.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> SaveEncodings => TextFile.SaveEncodings;
|
||||
public IReadOnlyList<string> SaveNewLines => TextFile.SaveNewLines;
|
||||
|
||||
[ObservableProperty] private string _saveEncoding = TextFile.KeepOriginal;
|
||||
[ObservableProperty] private string _saveNewLine = TextFile.KeepOriginal;
|
||||
|
||||
/// <summary>
|
||||
/// 지금 캐럿이 있는 <b>차이 덩어리</b> 전체를 BASE 내용으로 바꾼다.
|
||||
///
|
||||
/// 줄 하나씩만 되면 연속 다섯 줄을 다섯 번 눌러야 한다 - WinMerge·Beyond Compare 가
|
||||
/// 덩어리를 기본 단위로 쓰는 이유다. 덩어리는 "차이로 잡힌 줄이 이어지는 구간"이다.
|
||||
/// </summary>
|
||||
private bool CanAcceptBlock() => BlockAround(CaretRow).Count > 0;
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanAcceptBlock))]
|
||||
private void AcceptBlock()
|
||||
{
|
||||
var block = BlockAround(CaretRow);
|
||||
if (block.Count == 0) return;
|
||||
|
||||
foreach (var line in block) Apply(line, requery: false);
|
||||
|
||||
AfterEdit();
|
||||
MessageUtil.ShowSuccessToast($"{block.Count}줄 반영");
|
||||
}
|
||||
|
||||
/// <summary>그 줄이 속한 차이 덩어리. 차이가 아니면 빈 목록이다.</summary>
|
||||
private List<MergeLine> BlockAround(int row)
|
||||
{
|
||||
var found = new List<MergeLine>();
|
||||
if (row < 0 || row >= Lines.Count) return found;
|
||||
if (!Lines[row].CanTakeBase) return found;
|
||||
|
||||
var first = row;
|
||||
while (first > 0 && Lines[first - 1].CanTakeBase) first--;
|
||||
|
||||
var last = row;
|
||||
while (last + 1 < Lines.Count && Lines[last + 1].CanTakeBase) last++;
|
||||
|
||||
for (var i = first; i <= last; i++) found.Add(Lines[i]);
|
||||
return found;
|
||||
}
|
||||
|
||||
// ================= 자동 병합 · 충돌만 보기 =================
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -955,6 +955,7 @@
|
||||
Background="{DynamicResource Ubisam.Brush.Input.Background}"
|
||||
BorderBrush="{DynamicResource Ubisam.Brush.Accent}" BorderThickness="1"
|
||||
Effect="{DynamicResource Ubisam.Effect.Popup}">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBox x:Name="FindBox" Width="180" Height="24" MinHeight="24"
|
||||
FontSize="11" VerticalContentAlignment="Center"
|
||||
@ -977,6 +978,22 @@
|
||||
FontSize="10" Background="Transparent" BorderThickness="0"
|
||||
ToolTip="닫기 (Esc)"/>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 바꾸기 줄은 Ctrl+H 로 열 때만 뜬다. 찾기만 할 때 자리를 뺏지 않는다. -->
|
||||
<StackPanel x:Name="ReplaceRow" Orientation="Horizontal" Margin="0,4,0,0"
|
||||
Visibility="Collapsed">
|
||||
<TextBox x:Name="ReplaceBox" Width="180" Height="24" MinHeight="24"
|
||||
FontSize="11" VerticalContentAlignment="Center"
|
||||
ToolTip="바꿀 말"/>
|
||||
<Button Content="바꾸기" Click="Replace_Click" Padding="8,0" MinHeight="24"
|
||||
FontSize="11" Margin="6,0,2,0"
|
||||
ToolTip="고른 자리를 바꾸고 다음으로 넘어갑니다"/>
|
||||
<Button Content="모두" Click="ReplaceAll_Click" Padding="8,0" MinHeight="24"
|
||||
FontSize="11"
|
||||
ToolTip="글 전체에서 한 번에 바꿉니다 (Ctrl+Z 로 되돌립니다)"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
@ -1224,6 +1241,24 @@
|
||||
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"
|
||||
ToolTip="선택한 줄을 REMOTE(Theirs) 내용으로"/>
|
||||
|
||||
<Button Content="이 덩어리 반영" Command="{Binding AcceptBlockCommand}"
|
||||
MinHeight="30" Margin="2,4,2,0" FontSize="11"
|
||||
ToolTip="차이가 이어지는 구간을 한 번에 반영합니다 (Alt+Shift+Right)"/>
|
||||
|
||||
<!--
|
||||
저장 형식. 기본은 "원본 유지"라 지금까지와 똑같이 나간다.
|
||||
사내 소스에 CP949 가 섞여 있어, 새로 만드는 파일과 형식이 갈릴 때 여기서 맞춘다.
|
||||
-->
|
||||
<TextBlock Text="저장 형식" Style="{StaticResource Muted}" FontSize="11" Margin="0,12,0,4"/>
|
||||
<UniformGrid Columns="2">
|
||||
<ComboBox ItemsSource="{Binding SaveEncodings}" SelectedItem="{Binding SaveEncoding}"
|
||||
MinHeight="26" FontSize="11" Margin="2,0"
|
||||
ToolTip="저장할 때 쓸 인코딩"/>
|
||||
<ComboBox ItemsSource="{Binding SaveNewLines}" SelectedItem="{Binding SaveNewLine}"
|
||||
MinHeight="26" FontSize="11" Margin="2,0"
|
||||
ToolTip="저장할 때 쓸 줄바꿈"/>
|
||||
</UniformGrid>
|
||||
|
||||
<!--
|
||||
3-Way 흐름. "안 겹치는 변경은 자동으로, 사람은 충돌만" 이 순서다.
|
||||
남은 충돌이 0 이 되면 그 자리에서 다 됐다고 알려 준다.
|
||||
|
||||
@ -7,6 +7,7 @@ using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using MergeHub.Models;
|
||||
using MergeHub.Services;
|
||||
using UbisamBase.Core.Messaging;
|
||||
using MergeHub.ViewModels;
|
||||
|
||||
namespace MergeHub.Views
|
||||
@ -720,7 +721,10 @@ namespace MergeHub.Views
|
||||
vm.ToggleBookmarkCommand.Execute(vm.SelectedLine);
|
||||
return true;
|
||||
|
||||
case ShortcutAction.Find: OpenFind(); return true;
|
||||
case ShortcutAction.AcceptBlock: return Run(vm.AcceptBlockCommand);
|
||||
|
||||
case ShortcutAction.Find: OpenFind(showReplace: false); return true;
|
||||
case ShortcutAction.Replace: OpenFind(showReplace: true); return true;
|
||||
case ShortcutAction.FindNext: FindStep(forward: true); return true;
|
||||
case ShortcutAction.FindPrev: FindStep(forward: false); return true;
|
||||
|
||||
@ -755,15 +759,16 @@ namespace MergeHub.Views
|
||||
/// 찾기 칸을 연다. 글자를 골라 둔 채로 눌렀으면 그 말을 그대로 채워 준다 -
|
||||
/// 화면에서 눈에 띈 이름을 다시 타이핑하지 않게 하려는 것이다.
|
||||
/// </summary>
|
||||
private void OpenFind()
|
||||
private void OpenFind(bool showReplace)
|
||||
{
|
||||
FindBar.Visibility = Visibility.Visible;
|
||||
ReplaceRow.Visibility = showReplace ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
var picked = LocalEditor.SelectedText;
|
||||
if (!string.IsNullOrEmpty(picked) && !picked.Contains("\n")) FindBox.Text = picked;
|
||||
|
||||
FindBox.Focus();
|
||||
FindBox.SelectAll();
|
||||
if (showReplace) { ReplaceBox.Focus(); ReplaceBox.SelectAll(); }
|
||||
else { FindBox.Focus(); FindBox.SelectAll(); }
|
||||
}
|
||||
|
||||
private void CloseFind_Click(object sender, RoutedEventArgs e) => CloseFind();
|
||||
@ -778,6 +783,71 @@ namespace MergeHub.Views
|
||||
private void FindNext_Click(object sender, RoutedEventArgs e) => FindStep(forward: true);
|
||||
private void FindPrev_Click(object sender, RoutedEventArgs e) => FindStep(forward: false);
|
||||
|
||||
/// <summary>
|
||||
/// 지금 고른 자리를 바꾸고 다음으로 넘어간다.
|
||||
///
|
||||
/// 고른 자리가 찾는 말이 아니면 바꾸지 않고 <b>먼저 찾기만 한다</b> -
|
||||
/// 엉뚱한 자리를 덮어쓰는 것보다 한 번 더 누르게 하는 편이 낫다.
|
||||
/// </summary>
|
||||
private void Replace_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var needle = FindBox.Text;
|
||||
if (string.IsNullOrEmpty(needle)) return;
|
||||
|
||||
var how = FindMatchCase.IsChecked == true
|
||||
? StringComparison.Ordinal
|
||||
: StringComparison.OrdinalIgnoreCase;
|
||||
|
||||
if (LocalEditor.SelectionLength == needle.Length &&
|
||||
string.Equals(LocalEditor.SelectedText, needle, how))
|
||||
{
|
||||
LocalEditor.SelectedText = ReplaceBox.Text;
|
||||
}
|
||||
|
||||
FindStep(forward: true);
|
||||
}
|
||||
|
||||
/// <summary>글 전체에서 한 번에 바꾼다. 되돌리기는 편집기의 Ctrl+Z 가 맡는다.</summary>
|
||||
private void ReplaceAll_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var needle = FindBox.Text;
|
||||
if (string.IsNullOrEmpty(needle)) return;
|
||||
|
||||
var how = FindMatchCase.IsChecked == true
|
||||
? StringComparison.Ordinal
|
||||
: StringComparison.OrdinalIgnoreCase;
|
||||
|
||||
var text = LocalEditor.Text;
|
||||
var count = CountMatches(text, needle, how);
|
||||
if (count == 0) { FindStatus.Text = "없음"; return; }
|
||||
|
||||
var caret = LocalEditor.CaretIndex;
|
||||
LocalEditor.Text = ReplaceAll(text, needle, ReplaceBox.Text, how);
|
||||
LocalEditor.CaretIndex = Math.Min(caret, LocalEditor.Text.Length);
|
||||
|
||||
FindStatus.Text = $"{count}개 바꿈";
|
||||
MessageUtil.ShowSuccessToast($"{count}개를 바꿨습니다");
|
||||
}
|
||||
|
||||
private static string ReplaceAll(string text, string needle, string with, StringComparison how)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder(text.Length);
|
||||
var at = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var next = text.IndexOf(needle, at, how);
|
||||
if (next < 0) break;
|
||||
|
||||
sb.Append(text, at, next - at).Append(with);
|
||||
at = next + needle.Length;
|
||||
}
|
||||
|
||||
sb.Append(text, at, text.Length - at);
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>찾기 칸에서 Enter 는 다음, Shift+Enter 는 이전, Esc 는 닫기.</summary>
|
||||
private void FindBox_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
@ -804,7 +874,7 @@ namespace MergeHub.Views
|
||||
var needle = FindBox.Text;
|
||||
if (string.IsNullOrEmpty(needle))
|
||||
{
|
||||
OpenFind();
|
||||
OpenFind(showReplace: ReplaceRow.Visibility == Visibility.Visible);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user