using System; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; using AxCopilot.Models; using AxCopilot.SDK; using AxCopilot.Services; namespace AxCopilot.Handlers; public class SnippetHandler : IActionHandler { private struct INPUT { public uint type; public InputUnion u; } [StructLayout(LayoutKind.Explicit)] private struct InputUnion { [FieldOffset(0)] public KEYBDINPUT ki; } private struct KEYBDINPUT { public ushort wVk; public ushort wScan; public uint dwFlags; public uint time; public nint dwExtraInfo; } private readonly SettingsService _settings; public string? Prefix => ";"; public PluginMetadata Metadata => new PluginMetadata("Snippets", "텍스트 스니펫 — ; 뒤에 키워드 입력", "1.0", "AX"); public SnippetHandler(SettingsService settings) { _settings = settings; } public Task> GetItemsAsync(string query, CancellationToken ct) { List snippets = _settings.Settings.Snippets; if (!snippets.Any()) { return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("등록된 스니펫이 없습니다", "설정 → 스니펫 탭에서 추가하세요", null, null, null, "\ue70b"))); } string q = query.Trim().ToLowerInvariant(); List list = (from s in snippets where string.IsNullOrEmpty(q) || s.Key.ToLowerInvariant().Contains(q) || s.Name.ToLowerInvariant().Contains(q) select new LauncherItem((s.Name.Length > 0) ? s.Name : s.Key, TruncatePreview(s.Content), null, s, null, "\ue70b")).ToList(); if (list.Count == 0) { list.Add(new LauncherItem("'" + query + "'에 해당하는 스니펫 없음", "설정에서 새 스니펫을 추가하세요", null, null, null, "\ue70b")); } return Task.FromResult((IEnumerable)list); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (!(item.Data is SnippetEntry snippetEntry)) { return Task.CompletedTask; } try { string text = ExpandVariables(snippetEntry.Content); Clipboard.SetText(text); Task.Delay(100, ct).ContinueWith(delegate { ((DispatcherObject)Application.Current).Dispatcher.Invoke((Action)delegate { INPUT[] array = new INPUT[4] { new INPUT { type = 1u, u = new InputUnion { ki = new KEYBDINPUT { wVk = 17 } } }, new INPUT { type = 1u, u = new InputUnion { ki = new KEYBDINPUT { wVk = 86 } } }, new INPUT { type = 1u, u = new InputUnion { ki = new KEYBDINPUT { wVk = 86, dwFlags = 2u } } }, new INPUT { type = 1u, u = new InputUnion { ki = new KEYBDINPUT { wVk = 17, dwFlags = 2u } } } }; SendInput((uint)array.Length, array, Marshal.SizeOf()); }); }, ct, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); } catch (Exception ex) { LogService.Warn("스니펫 실행 실패: " + ex.Message); } return Task.CompletedTask; } private static string ExpandVariables(string content) { DateTime 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) { string text = content.Replace("\r\n", " ").Replace("\n", " ").Replace("\r", " "); return (text.Length > 60) ? (text.Substring(0, 57) + "…") : text; } [DllImport("user32.dll")] private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); }