Initial commit to new repository
This commit is contained in:
138
src/AxCopilot/Handlers/SnippetHandler.cs
Normal file
138
src/AxCopilot/Handlers/SnippetHandler.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System.Windows;
|
||||
using AxCopilot.Models;
|
||||
using AxCopilot.SDK;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Themes;
|
||||
|
||||
namespace AxCopilot.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 텍스트 스니펫 핸들러. ";" 프리픽스로 사용합니다.
|
||||
/// 예: ;addr → 미리 저장된 주소 텍스트를 클립보드에 복사 후 붙여넣기
|
||||
/// ;sig → 서명 텍스트 붙여넣기
|
||||
/// </summary>
|
||||
public class SnippetHandler : IActionHandler
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
public string? Prefix => ";";
|
||||
|
||||
public PluginMetadata Metadata => new(
|
||||
"Snippets",
|
||||
"텍스트 스니펫 — ; 뒤에 키워드 입력",
|
||||
"1.0",
|
||||
"AX");
|
||||
|
||||
public SnippetHandler(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<LauncherItem>> GetItemsAsync(string query, CancellationToken ct)
|
||||
{
|
||||
var snippets = _settings.Settings.Snippets;
|
||||
|
||||
if (!snippets.Any())
|
||||
{
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(
|
||||
[
|
||||
new LauncherItem(
|
||||
"등록된 스니펫이 없습니다",
|
||||
"설정 → 스니펫 탭에서 추가하세요",
|
||||
null, null, Symbol: Symbols.Snippet)
|
||||
]);
|
||||
}
|
||||
|
||||
var q = query.Trim().ToLowerInvariant();
|
||||
var results = snippets
|
||||
.Where(s => string.IsNullOrEmpty(q) ||
|
||||
s.Key.ToLowerInvariant().Contains(q) ||
|
||||
s.Name.ToLowerInvariant().Contains(q))
|
||||
.Select(s => new LauncherItem(
|
||||
s.Name.Length > 0 ? s.Name : s.Key,
|
||||
TruncatePreview(s.Content),
|
||||
null,
|
||||
s,
|
||||
Symbol: Symbols.Snippet))
|
||||
.ToList();
|
||||
|
||||
if (results.Count == 0)
|
||||
{
|
||||
results.Add(new LauncherItem(
|
||||
$"'{query}'에 해당하는 스니펫 없음",
|
||||
"설정에서 새 스니펫을 추가하세요",
|
||||
null, null, Symbol: Symbols.Snippet));
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<LauncherItem>>(results);
|
||||
}
|
||||
|
||||
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
|
||||
{
|
||||
if (item.Data is not SnippetEntry snippet) return Task.CompletedTask;
|
||||
|
||||
try
|
||||
{
|
||||
// 변수 치환: {date}, {time}, {datetime}
|
||||
var expanded = ExpandVariables(snippet.Content);
|
||||
|
||||
Clipboard.SetText(expanded);
|
||||
|
||||
// 100ms 후 Ctrl+V 시뮬레이션
|
||||
Task.Delay(100, ct).ContinueWith(_ =>
|
||||
{
|
||||
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var inputs = new[]
|
||||
{
|
||||
new INPUT { type = 1, u = new InputUnion { ki = new KEYBDINPUT { wVk = 0x11 } } }, // Ctrl down
|
||||
new INPUT { type = 1, u = new InputUnion { ki = new KEYBDINPUT { wVk = 0x56 } } }, // V down
|
||||
new INPUT { type = 1, u = new InputUnion { ki = new KEYBDINPUT { wVk = 0x56, dwFlags = 0x0002 } } }, // V up
|
||||
new INPUT { type = 1, u = new InputUnion { ki = new KEYBDINPUT { wVk = 0x11, dwFlags = 0x0002 } } }, // Ctrl up
|
||||
};
|
||||
SendInput((uint)inputs.Length, inputs, System.Runtime.InteropServices.Marshal.SizeOf<INPUT>());
|
||||
});
|
||||
}, ct, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogService.Warn($"스니펫 실행 실패: {ex.Message}");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// ─── 변수 치환 ─────────────────────────────────────────────────────────
|
||||
|
||||
private static string ExpandVariables(string content)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
return content
|
||||
.Replace("{date}", now.ToString("yyyy-MM-dd"))
|
||||
.Replace("{time}", now.ToString("HH:mm:ss"))
|
||||
.Replace("{datetime}", now.ToString("yyyy-MM-dd HH:mm:ss"))
|
||||
.Replace("{year}", now.Year.ToString())
|
||||
.Replace("{month}", now.Month.ToString("D2"))
|
||||
.Replace("{day}", now.Day.ToString("D2"));
|
||||
}
|
||||
|
||||
private static string TruncatePreview(string content)
|
||||
{
|
||||
var oneLine = content.Replace("\r\n", " ").Replace("\n", " ").Replace("\r", " ");
|
||||
return oneLine.Length > 60 ? oneLine[..57] + "…" : oneLine;
|
||||
}
|
||||
|
||||
// ─── P/Invoke (SendInput) ──────────────────────────────────────────────
|
||||
|
||||
[System.Runtime.InteropServices.DllImport("user32.dll")]
|
||||
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
|
||||
|
||||
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
private struct INPUT { public uint type; public InputUnion u; }
|
||||
|
||||
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]
|
||||
private struct InputUnion { [System.Runtime.InteropServices.FieldOffset(0)] public KEYBDINPUT ki; }
|
||||
|
||||
[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)]
|
||||
private struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public IntPtr dwExtraInfo; }
|
||||
}
|
||||
Reference in New Issue
Block a user