launch and manage windows terminal instances with raycast
raycast raycast-extension

aliases proof of concept

+176 -11
+152 -11
src/open-profile.tsx
··· 1 - import { Action, ActionPanel, Icon, Keyboard, List, closeMainWindow, getPreferenceValues } from "@raycast/api"; 1 + import { 2 + Action, 3 + ActionPanel, 4 + Form, 5 + Icon, 6 + Keyboard, 7 + List, 8 + LocalStorage, 9 + closeMainWindow, 10 + getPreferenceValues, 11 + showToast, 12 + useNavigation, 13 + } from "@raycast/api"; 14 + import { useForm } from "@raycast/utils"; 2 15 import { execFile } from "node:child_process"; 3 16 import fs from "node:fs"; 4 17 import os from "node:os"; 5 - import { useState } from "react"; 18 + import { useEffect, useState } from "react"; 6 19 import { Preferences } from "./types/preferences"; 7 - import { Profile, WindowsTerminalSettings, Folder } from "./types/windows-terminal"; 20 + import { Profile, WindowsTerminalSettings, Folder, FolderEntry } from "./types/windows-terminal"; 21 + import { getAllProfilePreferences, getProfilePreferences } from "./utils/profile-preferences"; 22 + import React from "react"; 8 23 9 24 const preferences = getPreferenceValues<Preferences>(); 10 25 const PROFILES = JSON.parse( 11 26 fs.readFileSync(`C:\\Users\\${os.userInfo().username}${preferences.release}`, "utf8"), 12 27 ) as WindowsTerminalSettings; 13 28 14 - function Actions(props: { profile: Profile }) { 29 + // TEACH ME HOW TO SUSPEND 30 + // ------------------------------------------------------------------------- // 31 + // A component was suspended by an uncached promise. Creating promises inside a 32 + // Client Component or hook is not yet supported, except via a 33 + // Suspense-compatible library or framework. 34 + // ------------------------------------------------------------------------- // 35 + // this does work despite the error, though. 36 + const EditAlias = React.memo(async (props: { profile: Profile }) => { 37 + console.log("INVOK"); 38 + const { pop } = useNavigation(); 39 + const [getAlias, setAlias] = useState<string>(""); 40 + 41 + const { handleSubmit, itemProps, setValue } = useForm<{ alias: string }>({ 42 + async onSubmit(values: { alias: string }) { 43 + await LocalStorage.setItem(props.profile.guid, JSON.stringify({ alias: values.alias })); 44 + pop(); 45 + showToast({ title: `Alias set for ${props.profile.name}` }); 46 + }, 47 + validation: { 48 + alias(value) { 49 + if (!value) { 50 + return "An alias is required"; 51 + } else if (value.length >= 12) { 52 + return "Alias is too long"; 53 + } 54 + }, 55 + }, 56 + }); 57 + 58 + useEffect(() => { 59 + async function load() { 60 + const profile = await getProfilePreferences(props.profile.guid); 61 + setAlias(profile.alias); 62 + // setValue("alias", getAlias); 63 + console.log(getAlias); 64 + } 65 + load(); 66 + }, [props.profile.guid]); 67 + 68 + return ( 69 + <Form 70 + actions={ 71 + <ActionPanel> 72 + <Action.SubmitForm 73 + icon={Icon.Pencil} 74 + title={getAlias ? "Update Alias" : "Set Alias"} 75 + onSubmit={handleSubmit} 76 + /> 77 + {getAlias ? ( 78 + <Action.SubmitForm 79 + icon={Icon.Trash} 80 + title="Delete Alias" 81 + shortcut={Keyboard.Shortcut.Common.Remove} 82 + style={Action.Style.Destructive} 83 + onSubmit={async () => { 84 + await LocalStorage.removeItem(props.profile.guid); 85 + // when the profile preferences gets extended, we will use this instead 86 + // await LocalStorage.setItem(props.profile.guid, JSON.stringify({ alias: "" })); 87 + pop(); 88 + showToast({ title: `Deleted alias for ${props.profile.name}` }); 89 + }} 90 + /> 91 + ) : null} 92 + </ActionPanel> 93 + } 94 + > 95 + <Form.TextField placeholder={`Edit alias for ${props.profile.name}...`} autoFocus={true} {...itemProps.alias} /> 96 + <Form.Description 97 + text={`This will be the new alias for ${props.profile.name}. You will be able to search for this profile in this extension using the alias you have set. This will not sync to Windows Terminal, or make it accessible in root search.`} 98 + /> 99 + </Form> 100 + ); 101 + }); 102 + 103 + function Actions(props: { profile: Profile; alias: string }) { 104 + const { push } = useNavigation(); 15 105 return ( 16 106 <ActionPanel title={props.profile.name}> 17 107 <Action ··· 50 140 /> 51 141 )} 52 142 <ActionPanel.Section> 143 + <Action 144 + icon={Icon.TextCursor} 145 + title={props.alias ? "Update Alias…" : "Set Alias…"} 146 + shortcut={{ modifiers: ["ctrl", "shift"], key: "," }} 147 + onAction={() => { 148 + push(<EditAlias profile={props.profile} />); 149 + }} 150 + /> 151 + </ActionPanel.Section> 152 + <ActionPanel.Section> 53 153 <Action.Open 54 154 icon={Icon.Code} 55 155 shortcut={Keyboard.Shortcut.Common.Edit} ··· 126 226 127 227 export default function Command() { 128 228 const [getFilter, setFilter] = useState("all"); 229 + const [getAliases, setAliases] = useState<{ [key: string]: { alias: string } }>({}); 230 + 231 + useEffect(() => { 232 + async function load() { 233 + const profiles = await getAllProfilePreferences(); 234 + setAliases(profiles); 235 + } 236 + load(); 237 + }, []); 129 238 130 239 return ( 131 240 <List ··· 231 340 keywords={ 232 341 item.guid === "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}" || // Windows PowerShell 1.0 (comes with Windows) 233 342 item.guid === "{574e775e-4f2a-5b96-ac1e-a2962a402336}" // Windows Powershell 7.0+ 234 - ? ["pwsh", "ps", "posh"] 343 + ? ["pwsh", "ps", "posh", getAliases[item.guid] ? getAliases[item.guid].alias : ""] 235 344 : item.guid === "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}" 236 - ? ["cmd"] 237 - : [] 345 + ? ["cmd", getAliases[item.guid] ? getAliases[item.guid].alias : ""] 346 + : [ 347 + getAliases[item.guid] ? getAliases[item.guid].alias : "" 348 + ] 349 + } 350 + accessories={getAliases[item.guid] ? [{ tag: { value: getAliases[item.guid].alias } }] : undefined} 351 + actions={ 352 + <Actions profile={item} alias={getAliases[item.guid] ? getAliases[item.guid].alias : ""} /> 238 353 } 239 - actions={<Actions profile={item} />} 240 354 /> 241 355 ))} 242 356 </List.Section> ··· 247 361 {PROFILES.profiles.list 248 362 .filter((item) => item.hidden !== true && item.source === "Windows.Terminal.SSH") 249 363 .map((item) => ( 250 - <List.Item key={item.guid} icon={Icon.Network} title={item.name} actions={<Actions profile={item} />} /> 364 + <List.Item 365 + key={item.guid} 366 + icon={Icon.Network} 367 + title={item.name} 368 + keywords={[getAliases[item.guid] ? getAliases[item.guid].alias : ""]} 369 + accessories={getAliases[item.guid] ? [{ tag: { value: getAliases[item.guid].alias } }] : undefined} 370 + actions={ 371 + <Actions profile={item} alias={getAliases[item.guid] ? getAliases[item.guid].alias : ""} /> 372 + } 373 + /> 251 374 ))} 252 375 </List.Section> 253 376 ) : null} ··· 258 381 {PROFILES.profiles.list 259 382 .filter((item) => item.hidden !== true && item.source === "Windows.Terminal.VisualStudio") 260 383 .map((item) => ( 261 - <List.Item key={item.guid} icon={Icon.Hammer} title={item.name} actions={<Actions profile={item} />} /> 384 + <List.Item 385 + key={item.guid} 386 + icon={Icon.Hammer} 387 + title={item.name} 388 + keywords={[getAliases[item.guid] ? getAliases[item.guid].alias : ""]} 389 + accessories={getAliases[item.guid] ? [{ tag: { value: getAliases[item.guid].alias } }] : undefined} 390 + actions={ 391 + <Actions profile={item} alias={getAliases[item.guid] ? getAliases[item.guid].alias : ""} /> 392 + } 393 + /> 262 394 ))} 263 395 </List.Section> 264 396 ) : null} ··· 274 406 item.hidden !== true && (item.source === "Microsoft.WSL" || item.source === "Windows.Terminal.Wsl"), 275 407 ) 276 408 .map((item) => ( 277 - <List.Item key={item.guid} icon={Icon.HardDrive} title={item.name} actions={<Actions profile={item} />} /> 409 + <List.Item 410 + key={item.guid} 411 + icon={Icon.HardDrive} 412 + title={item.name} 413 + keywords={[getAliases[item.guid] ? getAliases[item.guid].alias : ""]} 414 + accessories={getAliases[item.guid] ? [{ tag: { value: getAliases[item.guid].alias } }] : undefined} 415 + actions={ 416 + <Actions profile={item} alias={getAliases[item.guid] ? getAliases[item.guid].alias : ""} /> 417 + } 418 + /> 278 419 ))} 279 420 </List.Section> 280 421 ) : null}
+24
src/utils/profile-preferences.ts
··· 1 + import { LocalStorage } from "@raycast/api"; 2 + 3 + export async function getProfilePreferences(guid: string): Promise<{ alias: string }> { 4 + const profile = await LocalStorage.getItem<string>(guid); 5 + 6 + if (profile === undefined) { 7 + return { 8 + alias: "", 9 + }; 10 + } else { 11 + return JSON.parse(profile); 12 + } 13 + } 14 + 15 + export async function getAllProfilePreferences(): Promise<{ [key: string]: { alias: string } }> { 16 + const profiles = await LocalStorage.allItems<{ [key: string]: string }>(); 17 + const reconstructedProfiles: { [key: string]: { alias: string } } = {}; 18 + 19 + for (const i in profiles) { 20 + reconstructedProfiles[i] = JSON.parse(profiles[i]); 21 + } 22 + 23 + return reconstructedProfiles; 24 + }