; Editor.ahk — Éditeur de prompt intégré (architecture §9.2). ; Métadonnées + corps + déclaration des variables, validation en direct, bouton Tester. class PromptEditor { GuiObj := "" Item := "" ; PromptItem en cours d'édition ("" = nouveau, créé à l'enregistrement) PendingVars := [] ; déclarations de variables en cours F := Map() ; contrôles nommés OnSaved := "" ; Func(item) Show(item, onSaved := "") { this.Item := item this.OnSaved := onSaved this.PendingVars := [] if (item != "" && item.Meta.Has("variables")) { for v in item.Meta["variables"] this.PendingVars.Push(v) } if (this.GuiObj != "") this.GuiObj.Destroy() g := Gui("-MaximizeBox +Resize", item = "" ? "Nouveau prompt" : "Modifier — " item.Title) g.SetFont("s10", "Segoe UI") this.GuiObj := g F := this.F g.Add("Text", "xm w90", "Titre :") F["title"] := g.Add("Edit", "x+5 w420", item != "" ? item.Title : "") g.Add("Text", "xm w90", "Catégorie :") cats := App.Store.Categories() F["category"] := g.Add("ComboBox", "x+5 w420", cats) F["category"].Text := item != "" ? item.Category : "Sans catégorie" g.Add("Text", "xm w90", "Étiquettes :") F["tags"] := g.Add("Edit", "x+5 w420", item != "" ? Join(item.Tags, ", ") : "") g.Add("Text", "xm w90", "Profils :") F["targets"] := g.Add("Edit", "x+5 w420", item != "" ? Join(item.Targets, ", ") : "") F["favorite"] := g.Add("CheckBox", "xm", "Favori ★") F["favorite"].Value := item != "" ? item.Favorite : 0 F["pinned"] := g.Add("Edit", "x+30 w40 Number", item != "" ? item.Pinned : 0) g.Add("Text", "x+5", "Épinglé n° (0-9)") F["sensitive"] := g.Add("CheckBox", "x+30", "Sensible (vider le presse-papiers après)") F["sensitive"].Value := item != "" ? item.Sensitive : 0 g.Add("Text", "xm w90", "Raccourci :") F["hotkey"] := g.Add("Edit", "x+5 w200", item != "" ? item.Hotkey : "") g.Add("Text", "x+15 w90", "Hotstring :") F["hotstring"] := g.Add("Edit", "x+5 w200", item != "" ? item.Hotstring : "") g.Add("Text", "xm", "Corps du prompt ({{variable}} pour un emplacement, Ctrl+J pour insérer) :") F["body"] := g.Add("Edit", "xm w760 r16 -Wrap", item != "" ? item.Body : "") F["varsInfo"] := g.Add("Text", "xm w760", "") this.RefreshVarsInfo() btnVar := g.Add("Button", "xm w200", "Insérer une variable (Ctrl+J)") btnVar.OnEvent("Click", (*) => this.InsertVariable()) btnDeclare := g.Add("Button", "x+10 w200", "Déclarer une variable…") btnDeclare.OnEvent("Click", (*) => this.DeclareVariable()) btnTest := g.Add("Button", "x+10 w140", "Tester") btnTest.OnEvent("Click", (*) => this.TestPrompt()) btnExt := g.Add("Button", "xm w200", "Ouvrir dans l'éditeur externe") btnExt.OnEvent("Click", (*) => this.OpenExternal()) btnSave := g.Add("Button", "x+10 w160 Default", "Enregistrer") btnSave.OnEvent("Click", (*) => this.Save()) btnCancel := g.Add("Button", "x+10 w120", "Annuler") btnCancel.OnEvent("Click", (*) => this.GuiObj.Destroy()) g.OnEvent("Close", (*) => this.GuiObj.Destroy()) ; Ctrl+J dans l'éditeur. HotIfWinActive("ahk_id " g.Hwnd) Hotkey("^j", (*) => this.InsertVariable(), "On") HotIfWinActive() g.Show("w800") } RefreshVarsInfo() { names := [] for v in this.PendingVars { if v.Has("name") names.Push(v["name"] " (" (v.Has("type") ? v["type"] : "text") ")") } used := VariableEngine.FindVars(this.F["body"].Value) warns := [] for u in used { if VariableEngine.IsAuto(u) continue declared := false for v in this.PendingVars { if (v.Has("name") && StrLower(String(v["name"])) = u) { declared := true break } } if !declared warns.Push("« " u " » utilisé mais non déclaré (traité comme texte libre)") } txt := "Variables déclarées : " (names.Length ? Join(names, ", ") : "aucune") if (warns.Length) txt .= " · ⚠ " Join(warns, " ; ") this.F["varsInfo"].Text := txt } InsertVariable() { ib := InputBox("Nom de la variable à insérer (ex. ton, langage) :", "Insérer une variable") if (ib.Result != "OK" || Trim(ib.Value) = "") return name := StrLower(Trim(ib.Value)) body := this.F["body"] body.Focus() SendText("{{" name "}}") ; Si elle n'est pas déclarée, proposer de la déclarer. for v in this.PendingVars { if (v.Has("name") && StrLower(String(v["name"])) = name) return } r := MsgBox("La variable « " name " » n'est pas déclarée. La déclarer maintenant ?", "PromptKey", "YesNo") if (r = "Yes") this.DeclareVariable(name) this.RefreshVarsInfo() } DeclareVariable(presetName := "") { ibName := InputBox("Nom de la variable :", "Déclarer une variable", , presetName) if (ibName.Result != "OK" || Trim(ibName.Value) = "") return name := StrLower(Trim(ibName.Value)) ibLabel := InputBox("Libellé affiché dans le formulaire :", "Déclarer une variable", , name) if (ibLabel.Result != "OK") return ibType := InputBox("Type (text, multiline, enum, number, date, bool, password) :", "Déclarer une variable", , "text") if (ibType.Result != "OK") return type := StrLower(Trim(ibType.Value)) v := Map("name", name, "label", ibLabel.Value, "type", type) if (type = "enum") { ibChoices := InputBox("Choix, séparés par des virgules :", "Déclarer une variable") if (ibChoices.Result != "OK") return choices := [] for c in StrSplit(ibChoices.Value, ",") choices.Push(Trim(c)) v["choices"] := choices if (choices.Length) v["default"] := choices[1] } else { ibDef := InputBox("Valeur par défaut (optionnel) :", "Déclarer une variable") if (ibDef.Result = "OK" && ibDef.Value != "") v["default"] := ibDef.Value } ; Remplacer une déclaration existante du même nom. newVars := [] for existing in this.PendingVars { if !(existing.Has("name") && StrLower(String(existing["name"])) = name) newVars.Push(existing) } newVars.Push(v) this.PendingVars := newVars this.RefreshVarsInfo() } ; Prévisualisation du prompt résolu avec les valeurs par défaut (aucune injection, §9.2). TestPrompt() { item := this.CollectToItem(false) if (item = "") return defs := VariableEngine.ManualVars(item) defaults := Map() for d in defs defaults[d["name"]] := d["default"] ctx := Map("selection", "(texte sélectionné d'exemple)", "clipboardText", "", "process", "application") resolved := VariableEngine.Resolve(item.Body, defaults, ctx) g := Gui("+AlwaysOnTop", "Aperçu — " item.Title) g.SetFont("s10", "Segoe UI") g.Add("Edit", "w640 r18 ReadOnly -Wrap", resolved["text"]) if (resolved["unresolved"].Length) g.Add("Text", "w640 cRed", "Variables non résolues : " Join(resolved["unresolved"], ", ")) btn := g.Add("Button", "Default w120", "Fermer") btn.OnEvent("Click", (*) => g.Destroy()) g.OnEvent("Escape", (*) => g.Destroy()) g.Show() } OpenExternal() { item := this.Item if (item = "" || item.Path = "") { TrayTip("Enregistrez d'abord le prompt pour l'ouvrir dans l'éditeur externe.", "PromptKey") return } editor := App.Settings.ExternalEditor try { if (editor != "") Run(editor " `"" item.Path "`"") else Run(item.Path) } catch as e { MsgBox("Ouverture impossible : " e.Message, "PromptKey") } } ; Construit / met à jour le PromptItem depuis les contrôles. Crée le fichier seulement si save=true et nouveau. CollectToItem(save) { F := this.F title := Trim(F["title"].Value) if (title = "") { MsgBox("Le titre est requis.", "PromptKey") return "" } item := this.Item if (item = "") { if !save item := PromptItem("", Trim(F["category"].Text), Map("meta", Map(), "body", "", "errors", [], "hasHeader", false)) else item := App.Store.CreateNew(title, Trim(F["category"].Text), F["body"].Value) } item.Title := title item.Category := Trim(F["category"].Text) item.Tags := SplitCsv(F["tags"].Value) item.Targets := SplitCsv(F["targets"].Value) item.Favorite := F["favorite"].Value item.Pinned := F["pinned"].Value != "" ? Integer(F["pinned"].Value) : 0 item.Sensitive := F["sensitive"].Value item.Hotkey := Trim(F["hotkey"].Value) item.Hotstring := Trim(F["hotstring"].Value) item.Body := F["body"].Value if (this.PendingVars.Length) item.Meta["variables"] := this.PendingVars else if item.Meta.Has("variables") item.Meta.Delete("variables") return item } Save() { item := this.CollectToItem(true) if (item = "") return App.Store.Save(item) this.Item := item TrayTip("Prompt « " item.Title " » enregistré.", "PromptKey") this.GuiObj.Destroy() App.RefreshAfterLibraryChange() if (this.OnSaved != "") this.OnSaved(item) } } SplitCsv(s) { out := [] for part in StrSplit(s, ",") { part := Trim(part) if (part != "") out.Push(part) } return out }