| b69ab31 | | | 1 | /** |
| b69ab31 | | | 2 | * Copyright (c) Meta Platforms, Inc. and affiliates. |
| b69ab31 | | | 3 | * |
| b69ab31 | | | 4 | * This source code is licensed under the MIT license found in the |
| b69ab31 | | | 5 | * LICENSE file in the root directory of this source tree. |
| b69ab31 | | | 6 | */ |
| b69ab31 | | | 7 | |
| b69ab31 | | | 8 | import type {ThemeColor} from './theme'; |
| b69ab31 | | | 9 | import type {PreferredSubmitCommand} from './types'; |
| b69ab31 | | | 10 | |
| b69ab31 | | | 11 | import {Button} from 'isl-components/Button'; |
| b69ab31 | | | 12 | import {Checkbox} from 'isl-components/Checkbox'; |
| b69ab31 | | | 13 | import {Dropdown} from 'isl-components/Dropdown'; |
| b69ab31 | | | 14 | import {Icon} from 'isl-components/Icon'; |
| b69ab31 | | | 15 | import {Kbd} from 'isl-components/Kbd'; |
| b69ab31 | | | 16 | import {KeyCode, Modifier} from 'isl-components/KeyboardShortcuts'; |
| b69ab31 | | | 17 | import {Subtle} from 'isl-components/Subtle'; |
| b69ab31 | | | 18 | import {Tooltip} from 'isl-components/Tooltip'; |
| b69ab31 | | | 19 | import {useAtom, useAtomValue} from 'jotai'; |
| b69ab31 | | | 20 | import {Suspense} from 'react'; |
| b69ab31 | | | 21 | import {nullthrows, tryJsonParse} from 'shared/utils'; |
| b69ab31 | | | 22 | import { |
| b69ab31 | | | 23 | distantRebaseWarningEnabled, |
| b69ab31 | | | 24 | rebaseOffWarmWarningEnabled, |
| b69ab31 | | | 25 | rebaseOntoMasterWarningEnabled, |
| b69ab31 | | | 26 | } from './Commit'; |
| b69ab31 | | | 27 | import {condenseObsoleteStacks} from './CommitTreeList'; |
| b69ab31 | | | 28 | import {Column, Row} from './ComponentUtils'; |
| b69ab31 | | | 29 | import {confirmShouldSubmitEnabledAtom} from './ConfirmSubmitStack'; |
| b69ab31 | | | 30 | import {DropdownField, DropdownFields} from './DropdownFields'; |
| b69ab31 | | | 31 | import {useShowKeyboardShortcutsHelp} from './ISLShortcuts'; |
| b69ab31 | | | 32 | import {Link} from './Link'; |
| b69ab31 | | | 33 | import {RestackBehaviorSetting} from './RestackBehavior'; |
| b69ab31 | | | 34 | import {Setting} from './Setting'; |
| b69ab31 | | | 35 | import { |
| b69ab31 | | | 36 | currentExperimentalFeaturesList, |
| b69ab31 | | | 37 | hasExperimentalFeatures, |
| b69ab31 | | | 38 | } from './atoms/experimentalFeatureAtoms'; |
| b69ab31 | | | 39 | import {codeReviewProvider} from './codeReview/CodeReviewInfo'; |
| b69ab31 | | | 40 | import {showDiffNumberConfig} from './codeReview/DiffBadge'; |
| b69ab31 | | | 41 | import {SubmitAsDraftCheckbox} from './codeReview/DraftCheckbox'; |
| b69ab31 | | | 42 | import { |
| b69ab31 | | | 43 | branchPRsSupported, |
| b69ab31 | | | 44 | experimentalBranchPRsEnabled, |
| b69ab31 | | | 45 | overrideDisabledSubmitModes, |
| b69ab31 | | | 46 | } from './codeReview/github/branchPrState'; |
| b69ab31 | | | 47 | import {debugToolsEnabledState} from './debug/DebugToolsState'; |
| b69ab31 | | | 48 | import {externalMergeToolAtom} from './externalMergeTool'; |
| b69ab31 | | | 49 | import {t, T} from './i18n'; |
| b69ab31 | | | 50 | import {configBackedAtom, readAtom} from './jotaiUtils'; |
| b69ab31 | | | 51 | import {AutoResolveSettingCheckbox} from './mergeConflicts/state'; |
| b69ab31 | | | 52 | import {SetConfigOperation} from './operations/SetConfigOperation'; |
| b69ab31 | | | 53 | import {useRunOperation} from './operationsState'; |
| b69ab31 | | | 54 | import platform from './platform'; |
| b69ab31 | | | 55 | import {irrelevantCwdDisplayModeAtom} from './repositoryData'; |
| b69ab31 | | | 56 | import {renderCompactAtom, useZoomShortcut, zoomUISettingAtom} from './responsive'; |
| b69ab31 | | | 57 | import {mainCommandName, repositoryInfo} from './serverAPIState'; |
| b69ab31 | | | 58 | import {themeState, useThemeShortcut} from './theme'; |
| b69ab31 | | | 59 | |
| b69ab31 | | | 60 | import './SettingsTooltip.css'; |
| b69ab31 | | | 61 | import {enableSaplingDebugFlag, enableSaplingVerboseFlag} from './atoms/debugToolAtoms'; |
| b69ab31 | | | 62 | |
| b69ab31 | | | 63 | export function SettingsGearButton() { |
| b69ab31 | | | 64 | useThemeShortcut(); |
| b69ab31 | | | 65 | useZoomShortcut(); |
| b69ab31 | | | 66 | const showShortcutsHelp = useShowKeyboardShortcutsHelp(); |
| b69ab31 | | | 67 | return ( |
| b69ab31 | | | 68 | <Tooltip |
| b69ab31 | | | 69 | trigger="click" |
| b69ab31 | | | 70 | component={dismiss => ( |
| b69ab31 | | | 71 | <SettingsDropdown dismiss={dismiss} showShortcutsHelp={showShortcutsHelp} /> |
| b69ab31 | | | 72 | )} |
| b69ab31 | | | 73 | group="topbar" |
| b69ab31 | | | 74 | placement="bottom"> |
| b69ab31 | | | 75 | <Button icon data-testid="settings-gear-button"> |
| b69ab31 | | | 76 | <Icon icon="gear" /> |
| b69ab31 | | | 77 | </Button> |
| b69ab31 | | | 78 | </Tooltip> |
| b69ab31 | | | 79 | ); |
| b69ab31 | | | 80 | } |
| b69ab31 | | | 81 | |
| b69ab31 | | | 82 | function SettingsDropdown({ |
| b69ab31 | | | 83 | dismiss, |
| b69ab31 | | | 84 | showShortcutsHelp, |
| b69ab31 | | | 85 | }: { |
| b69ab31 | | | 86 | dismiss: () => unknown; |
| b69ab31 | | | 87 | showShortcutsHelp: () => unknown; |
| b69ab31 | | | 88 | }) { |
| b69ab31 | | | 89 | const [theme, setTheme] = useAtom(themeState); |
| b69ab31 | | | 90 | const [repoInfo, setRepoInfo] = useAtom(repositoryInfo); |
| b69ab31 | | | 91 | const runOperation = useRunOperation(); |
| b69ab31 | | | 92 | const [showDiffNumber, setShowDiffNumber] = useAtom(showDiffNumberConfig); |
| b69ab31 | | | 93 | return ( |
| b69ab31 | | | 94 | <DropdownFields title={<T>Settings</T>} icon="gear" data-testid="settings-dropdown"> |
| b69ab31 | | | 95 | <Button |
| b69ab31 | | | 96 | style={{justifyContent: 'center', gap: 0}} |
| b69ab31 | | | 97 | icon |
| b69ab31 | | | 98 | onClick={() => { |
| b69ab31 | | | 99 | dismiss(); |
| b69ab31 | | | 100 | showShortcutsHelp(); |
| b69ab31 | | | 101 | }}> |
| b69ab31 | | | 102 | <T |
| b69ab31 | | | 103 | replace={{ |
| b69ab31 | | | 104 | $shortcut: <Kbd keycode={KeyCode.QuestionMark} modifiers={[Modifier.SHIFT]} />, |
| b69ab31 | | | 105 | }}> |
| b69ab31 | | | 106 | View Keyboard Shortcuts - $shortcut |
| b69ab31 | | | 107 | </T> |
| b69ab31 | | | 108 | </Button> |
| b69ab31 | | | 109 | {platform.theme != null ? null : ( |
| b69ab31 | | | 110 | <Setting title={<T>Theme</T>}> |
| b69ab31 | | | 111 | <Dropdown |
| b69ab31 | | | 112 | options={ |
| b69ab31 | | | 113 | [ |
| b69ab31 | | | 114 | {value: 'light', name: 'Light'}, |
| b69ab31 | | | 115 | {value: 'dark', name: 'Dark'}, |
| b69ab31 | | | 116 | ] as Array<{value: ThemeColor; name: string}> |
| b69ab31 | | | 117 | } |
| b69ab31 | | | 118 | value={theme} |
| b69ab31 | | | 119 | onChange={event => setTheme(event.currentTarget.value as ThemeColor)} |
| b69ab31 | | | 120 | /> |
| b69ab31 | | | 121 | <div style={{marginTop: 'var(--pad)'}}> |
| b69ab31 | | | 122 | <Subtle> |
| b69ab31 | | | 123 | <T>Toggle: </T> |
| b69ab31 | | | 124 | <Kbd keycode={KeyCode.T} modifiers={[Modifier.ALT]} /> |
| b69ab31 | | | 125 | </Subtle> |
| b69ab31 | | | 126 | </div> |
| b69ab31 | | | 127 | </Setting> |
| b69ab31 | | | 128 | )} |
| b69ab31 | | | 129 | |
| b69ab31 | | | 130 | <Setting title={<T>UI Scale</T>}> |
| b69ab31 | | | 131 | <ZoomUISetting /> |
| b69ab31 | | | 132 | </Setting> |
| b69ab31 | | | 133 | <Setting title={<T>Commits</T>}> |
| b69ab31 | | | 134 | <Column alignStart> |
| b69ab31 | | | 135 | <RenderCompactSetting /> |
| b69ab31 | | | 136 | <CondenseObsoleteSetting /> |
| b69ab31 | | | 137 | <RebaseOffWarmWarningSetting /> |
| b69ab31 | | | 138 | <DistantRebaseWarningSetting /> |
| b69ab31 | | | 139 | <RebaseOntoMasterWarningSetting /> |
| b69ab31 | | | 140 | <DeemphasizeIrrelevantCommitsSetting /> |
| b69ab31 | | | 141 | </Column> |
| b69ab31 | | | 142 | </Setting> |
| b69ab31 | | | 143 | <Setting title={<T>Conflicts</T>}> |
| b69ab31 | | | 144 | <AutoResolveSettingCheckbox /> |
| b69ab31 | | | 145 | <RestackBehaviorSetting /> |
| b69ab31 | | | 146 | </Setting> |
| b69ab31 | | | 147 | {/* TODO: enable this setting when there is actually a chocie to be made here. */} |
| b69ab31 | | | 148 | {/* <Setting |
| b69ab31 | | | 149 | title={<T>Language</T>} |
| b69ab31 | | | 150 | description={<T>Locale for translations used in the UI. Currently only en supported.</T>}> |
| b69ab31 | | | 151 | <Dropdown value="en" options=['en'] /> |
| b69ab31 | | | 152 | </Setting> */} |
| b69ab31 | | | 153 | {repoInfo == null ? ( |
| b69ab31 | | | 154 | <Icon icon="loading" /> |
| b69ab31 | | | 155 | ) : repoInfo.codeReviewSystem.type === 'github' ? ( |
| b69ab31 | | | 156 | <Setting |
| b69ab31 | | | 157 | title={<T>Preferred Code Review Submit Method</T>} |
| b69ab31 | | | 158 | description={ |
| b69ab31 | | | 159 | <> |
| b69ab31 | | | 160 | <T>How to submit code for code review on GitHub.</T>{' '} |
| b69ab31 | | | 161 | {/* TODO: update this to document branchign PRs */} |
| b69ab31 | | | 162 | <Link href="https://sapling-scm.com/docs/git/intro#pull-requests"> |
| b69ab31 | | | 163 | <T>Learn More</T> |
| b69ab31 | | | 164 | </Link> |
| b69ab31 | | | 165 | </> |
| b69ab31 | | | 166 | }> |
| b69ab31 | | | 167 | <Dropdown |
| b69ab31 | | | 168 | value={repoInfo.preferredSubmitCommand ?? 'not set'} |
| b69ab31 | | | 169 | options={(repoInfo.preferredSubmitCommand == null |
| b69ab31 | | | 170 | ? [{value: 'not set', name: '(not set)'}] |
| b69ab31 | | | 171 | : [] |
| b69ab31 | | | 172 | ).concat([ |
| b69ab31 | | | 173 | {value: 'ghstack', name: 'sl ghstack (stacked PRs)'}, |
| b69ab31 | | | 174 | {value: 'pr', name: 'sl pr (stacked PRs)'}, |
| b69ab31 | | | 175 | ...(readAtom(branchPRsSupported) |
| b69ab31 | | | 176 | ? [{value: 'push', name: 'sl push (branching PR)'}] |
| b69ab31 | | | 177 | : []), |
| b69ab31 | | | 178 | ])} |
| b69ab31 | | | 179 | onChange={event => { |
| b69ab31 | | | 180 | const value = (event as React.FormEvent<HTMLSelectElement>).currentTarget.value as |
| b69ab31 | | | 181 | | PreferredSubmitCommand |
| b69ab31 | | | 182 | | 'not set'; |
| b69ab31 | | | 183 | if (value === 'not set') { |
| b69ab31 | | | 184 | return; |
| b69ab31 | | | 185 | } |
| b69ab31 | | | 186 | |
| b69ab31 | | | 187 | runOperation( |
| b69ab31 | | | 188 | new SetConfigOperation('local', 'github.preferred_submit_command', value), |
| b69ab31 | | | 189 | ); |
| b69ab31 | | | 190 | setRepoInfo(info => ({...nullthrows(info), preferredSubmitCommand: value})); |
| b69ab31 | | | 191 | }} |
| b69ab31 | | | 192 | /> |
| b69ab31 | | | 193 | </Setting> |
| b69ab31 | | | 194 | ) : null} |
| b69ab31 | | | 195 | <Setting title={<T>Code Review</T>}> |
| b69ab31 | | | 196 | <div className="multiple-settings"> |
| b69ab31 | | | 197 | <Checkbox |
| b69ab31 | | | 198 | checked={showDiffNumber} |
| b69ab31 | | | 199 | onChange={checked => { |
| b69ab31 | | | 200 | setShowDiffNumber(checked); |
| b69ab31 | | | 201 | }}> |
| b69ab31 | | | 202 | <T>Show copyable Diff / Pull Request numbers inline for each commit</T> |
| b69ab31 | | | 203 | </Checkbox> |
| b69ab31 | | | 204 | <ConfirmSubmitStackSetting /> |
| b69ab31 | | | 205 | <SubmitAsDraftCheckbox forceShow /> |
| b69ab31 | | | 206 | </div> |
| b69ab31 | | | 207 | </Setting> |
| b69ab31 | | | 208 | {platform.canCustomizeFileOpener && ( |
| b69ab31 | | | 209 | <Setting title={<T>Environment</T>}> |
| b69ab31 | | | 210 | <Column alignStart> |
| b69ab31 | | | 211 | <OpenFilesCmdSetting /> |
| b69ab31 | | | 212 | <ExternalMergeToolSetting /> |
| b69ab31 | | | 213 | </Column> |
| b69ab31 | | | 214 | </Setting> |
| b69ab31 | | | 215 | )} |
| b69ab31 | | | 216 | <Suspense>{platform.Settings == null ? null : <platform.Settings />}</Suspense> |
| b69ab31 | | | 217 | <DebugToolsField /> |
| b69ab31 | | | 218 | </DropdownFields> |
| b69ab31 | | | 219 | ); |
| b69ab31 | | | 220 | } |
| b69ab31 | | | 221 | |
| b69ab31 | | | 222 | function ConfirmSubmitStackSetting() { |
| b69ab31 | | | 223 | const [value, setValue] = useAtom(confirmShouldSubmitEnabledAtom); |
| b69ab31 | | | 224 | const provider = useAtomValue(codeReviewProvider); |
| b69ab31 | | | 225 | if (provider == null || !provider.supportSubmittingAsDraft) { |
| b69ab31 | | | 226 | return null; |
| b69ab31 | | | 227 | } |
| b69ab31 | | | 228 | return ( |
| b69ab31 | | | 229 | <Tooltip |
| b69ab31 | | | 230 | title={t( |
| b69ab31 | | | 231 | 'This lets you choose to submit as draft and provide an update message. ' + |
| b69ab31 | | | 232 | 'If false, no confirmation is shown and it will submit as draft if you previously ' + |
| b69ab31 | | | 233 | 'checked the submit as draft checkbox.', |
| b69ab31 | | | 234 | )}> |
| b69ab31 | | | 235 | <Checkbox |
| b69ab31 | | | 236 | checked={value} |
| b69ab31 | | | 237 | onChange={checked => { |
| b69ab31 | | | 238 | setValue(checked); |
| b69ab31 | | | 239 | }}> |
| b69ab31 | | | 240 | <T>Show confirmation when submitting a stack</T> |
| b69ab31 | | | 241 | </Checkbox> |
| b69ab31 | | | 242 | </Tooltip> |
| b69ab31 | | | 243 | ); |
| b69ab31 | | | 244 | } |
| b69ab31 | | | 245 | |
| b69ab31 | | | 246 | function RenderCompactSetting() { |
| b69ab31 | | | 247 | const [value, setValue] = useAtom(renderCompactAtom); |
| b69ab31 | | | 248 | return ( |
| b69ab31 | | | 249 | <Tooltip |
| b69ab31 | | | 250 | title={t( |
| b69ab31 | | | 251 | 'Render commits in the tree more compactly, by reducing spacing and not wrapping Diff info to multiple lines. ' + |
| b69ab31 | | | 252 | 'May require more horizontal scrolling.', |
| b69ab31 | | | 253 | )}> |
| b69ab31 | | | 254 | <Checkbox |
| b69ab31 | | | 255 | checked={value} |
| b69ab31 | | | 256 | onChange={checked => { |
| b69ab31 | | | 257 | setValue(checked); |
| b69ab31 | | | 258 | }}> |
| b69ab31 | | | 259 | <T>Compact Mode</T> |
| b69ab31 | | | 260 | </Checkbox> |
| b69ab31 | | | 261 | </Tooltip> |
| b69ab31 | | | 262 | ); |
| b69ab31 | | | 263 | } |
| b69ab31 | | | 264 | |
| b69ab31 | | | 265 | function CondenseObsoleteSetting() { |
| b69ab31 | | | 266 | const [value, setValue] = useAtom(condenseObsoleteStacks); |
| b69ab31 | | | 267 | return ( |
| b69ab31 | | | 268 | <Tooltip |
| b69ab31 | | | 269 | title={t( |
| b69ab31 | | | 270 | 'Visually condense a continuous stack of obsolete commits into just the top and bottom commits.', |
| b69ab31 | | | 271 | )}> |
| b69ab31 | | | 272 | <Checkbox |
| b69ab31 | | | 273 | data-testid="condense-obsolete-stacks" |
| b69ab31 | | | 274 | checked={value !== false} |
| b69ab31 | | | 275 | onChange={checked => { |
| b69ab31 | | | 276 | setValue(checked); |
| b69ab31 | | | 277 | }}> |
| b69ab31 | | | 278 | <T>Condense Obsolete Stacks</T> |
| b69ab31 | | | 279 | </Checkbox> |
| b69ab31 | | | 280 | </Tooltip> |
| b69ab31 | | | 281 | ); |
| b69ab31 | | | 282 | } |
| b69ab31 | | | 283 | |
| b69ab31 | | | 284 | function DeemphasizeIrrelevantCommitsSetting() { |
| b69ab31 | | | 285 | const [value, setValue] = useAtom(irrelevantCwdDisplayModeAtom); |
| b69ab31 | | | 286 | return ( |
| b69ab31 | | | 287 | <Tooltip |
| b69ab31 | | | 288 | title={t( |
| b69ab31 | | | 289 | 'How to display commits which only change files in an unrelated directory to your current working directory.\n', |
| b69ab31 | | | 290 | )}> |
| b69ab31 | | | 291 | <div className="dropdown-container setting-inline-dropdown"> |
| b69ab31 | | | 292 | <T>Cwd-Irrelevant Commits</T> |
| b69ab31 | | | 293 | <Dropdown<{value: typeof value; name: string}> |
| b69ab31 | | | 294 | data-testid="cwd-irrelevant-commits" |
| b69ab31 | | | 295 | options={[ |
| b69ab31 | | | 296 | {value: 'show', name: t('Show')}, |
| b69ab31 | | | 297 | {value: 'deemphasize', name: t('Deemphasize')}, |
| b69ab31 | | | 298 | {value: 'hide', name: t('Hide')}, |
| b69ab31 | | | 299 | ]} |
| b69ab31 | | | 300 | value={value} |
| b69ab31 | | | 301 | onChange={event => { |
| b69ab31 | | | 302 | setValue(event.currentTarget.value as typeof value); |
| b69ab31 | | | 303 | }} |
| b69ab31 | | | 304 | /> |
| b69ab31 | | | 305 | </div> |
| b69ab31 | | | 306 | </Tooltip> |
| b69ab31 | | | 307 | ); |
| b69ab31 | | | 308 | } |
| b69ab31 | | | 309 | |
| b69ab31 | | | 310 | function RebaseOffWarmWarningSetting() { |
| b69ab31 | | | 311 | const [value, setValue] = useAtom(rebaseOffWarmWarningEnabled); |
| b69ab31 | | | 312 | return ( |
| b69ab31 | | | 313 | <Tooltip |
| b69ab31 | | | 314 | title={t( |
| b69ab31 | | | 315 | 'Show a warning when rebasing off a commit that is not warm (i.e. not in the current stack).', |
| b69ab31 | | | 316 | )}> |
| b69ab31 | | | 317 | <Checkbox |
| b69ab31 | | | 318 | data-testid="rebase-off-warm-warning-enabled" |
| b69ab31 | | | 319 | checked={value} |
| b69ab31 | | | 320 | onChange={checked => { |
| b69ab31 | | | 321 | setValue(checked); |
| b69ab31 | | | 322 | }}> |
| b69ab31 | | | 323 | <T>Show Warning on Rebase Off Warm</T> |
| b69ab31 | | | 324 | </Checkbox> |
| b69ab31 | | | 325 | </Tooltip> |
| b69ab31 | | | 326 | ); |
| b69ab31 | | | 327 | } |
| b69ab31 | | | 328 | |
| b69ab31 | | | 329 | function DistantRebaseWarningSetting() { |
| b69ab31 | | | 330 | const [value, setValue] = useAtom(distantRebaseWarningEnabled); |
| b69ab31 | | | 331 | return ( |
| b69ab31 | | | 332 | <Tooltip |
| b69ab31 | | | 333 | title={t( |
| b69ab31 | | | 334 | 'Show a warning when rebasing onto a commit that is significantly older than the current commit.', |
| b69ab31 | | | 335 | )}> |
| b69ab31 | | | 336 | <Checkbox |
| b69ab31 | | | 337 | data-testid="distant-rebase-warning-enabled" |
| b69ab31 | | | 338 | checked={value} |
| b69ab31 | | | 339 | onChange={checked => { |
| b69ab31 | | | 340 | setValue(checked); |
| b69ab31 | | | 341 | }}> |
| b69ab31 | | | 342 | <T>Show Warning on Distant Rebase</T> |
| b69ab31 | | | 343 | </Checkbox> |
| b69ab31 | | | 344 | </Tooltip> |
| b69ab31 | | | 345 | ); |
| b69ab31 | | | 346 | } |
| b69ab31 | | | 347 | |
| b69ab31 | | | 348 | function RebaseOntoMasterWarningSetting() { |
| b69ab31 | | | 349 | const [value, setValue] = useAtom(rebaseOntoMasterWarningEnabled); |
| b69ab31 | | | 350 | return ( |
| b69ab31 | | | 351 | <Tooltip |
| b69ab31 | | | 352 | title={t( |
| b69ab31 | | | 353 | 'Show a warning when rebasing directly onto master branch, which can cause unexpected failures and slower builds.', |
| b69ab31 | | | 354 | )}> |
| b69ab31 | | | 355 | <Checkbox |
| b69ab31 | | | 356 | data-testid="rebase-master-warning-enabled" |
| b69ab31 | | | 357 | checked={value} |
| b69ab31 | | | 358 | onChange={checked => { |
| b69ab31 | | | 359 | setValue(checked); |
| b69ab31 | | | 360 | }}> |
| b69ab31 | | | 361 | <T>Show Warning on Rebase onto Master</T> |
| b69ab31 | | | 362 | </Checkbox> |
| b69ab31 | | | 363 | </Tooltip> |
| b69ab31 | | | 364 | ); |
| b69ab31 | | | 365 | } |
| b69ab31 | | | 366 | |
| b69ab31 | | | 367 | export const openFileCmdAtom = configBackedAtom<string | null>( |
| b69ab31 | | | 368 | 'isl.open-file-cmd', |
| b69ab31 | | | 369 | null, |
| b69ab31 | | | 370 | /* readonly */ true, |
| b69ab31 | | | 371 | /* use raw value */ true, |
| b69ab31 | | | 372 | ); |
| b69ab31 | | | 373 | |
| b69ab31 | | | 374 | function OpenFilesCmdSetting() { |
| b69ab31 | | | 375 | const cmdRaw = useAtomValue(openFileCmdAtom); |
| b69ab31 | | | 376 | const cmd = cmdRaw == null ? null : ((tryJsonParse(cmdRaw) as string | Array<string>) ?? cmdRaw); |
| b69ab31 | | | 377 | const cmdEl = |
| b69ab31 | | | 378 | cmd == null ? ( |
| b69ab31 | | | 379 | <T>OS Default Program</T> |
| b69ab31 | | | 380 | ) : ( |
| b69ab31 | | | 381 | <code>{Array.isArray(cmd) ? cmd.join(' ') : cmd}</code> |
| b69ab31 | | | 382 | ); |
| b69ab31 | | | 383 | return ( |
| b69ab31 | | | 384 | <Tooltip |
| b69ab31 | | | 385 | component={() => ( |
| b69ab31 | | | 386 | <div> |
| b69ab31 | | | 387 | <div> |
| b69ab31 | | | 388 | <T>You can configure how to open files from ISL via</T> |
| b69ab31 | | | 389 | </div> |
| b69ab31 | | | 390 | <pre>sl config --user isl.open-file-cmd "/path/to/command"</pre> |
| b69ab31 | | | 391 | <div> |
| b69ab31 | | | 392 | <T>or</T> |
| b69ab31 | | | 393 | </div> |
| b69ab31 | | | 394 | <pre>sl config --user isl.open-file-cmd '["cmd", "with", "args"]'</pre> |
| b69ab31 | | | 395 | </div> |
| b69ab31 | | | 396 | )}> |
| b69ab31 | | | 397 | <Row> |
| b69ab31 | | | 398 | <T replace={{$cmd: cmdEl}}>Open files in: $cmd</T> |
| b69ab31 | | | 399 | <Subtle> |
| b69ab31 | | | 400 | <T>How to configure?</T> |
| b69ab31 | | | 401 | </Subtle> |
| b69ab31 | | | 402 | <Icon icon="question" /> |
| b69ab31 | | | 403 | </Row> |
| b69ab31 | | | 404 | </Tooltip> |
| b69ab31 | | | 405 | ); |
| b69ab31 | | | 406 | } |
| b69ab31 | | | 407 | |
| b69ab31 | | | 408 | function ExternalMergeToolSetting() { |
| b69ab31 | | | 409 | const mergeTool = useAtomValue(externalMergeToolAtom); |
| b69ab31 | | | 410 | const cmdEl = mergeTool == null ? <T>None</T> : <code>{mergeTool}</code>; |
| b69ab31 | | | 411 | return ( |
| b69ab31 | | | 412 | <Tooltip |
| b69ab31 | | | 413 | component={() => ( |
| b69ab31 | | | 414 | <div> |
| b69ab31 | | | 415 | <div style={{alignItems: 'flex-start', maxWidth: 400}}> |
| b69ab31 | | | 416 | <T |
| b69ab31 | | | 417 | replace={{ |
| b69ab31 | | | 418 | $help: <code>sl help config.merge-tools</code>, |
| b69ab31 | | | 419 | $configedit: <code>sl config --edit</code>, |
| b69ab31 | | | 420 | $mymergetool: <code>merge-tools.mymergetool</code>, |
| b69ab31 | | | 421 | $uimerge: <code>ui.merge = mymergetool</code>, |
| b69ab31 | | | 422 | $gui: <code>merge-tools.mymergetool.gui</code>, |
| b69ab31 | | | 423 | $local: <code>--local</code>, |
| b69ab31 | | | 424 | $br: ( |
| b69ab31 | | | 425 | <> |
| b69ab31 | | | 426 | <br /> |
| b69ab31 | | | 427 | <br /> |
| b69ab31 | | | 428 | </> |
| b69ab31 | | | 429 | ), |
| b69ab31 | | | 430 | }}> |
| b69ab31 | | | 431 | You can configure Sapling and ISL to use a custom external merge tool, which is used |
| b69ab31 | | | 432 | when a merge conflict is detected.$br Define your tool with $configedit (or with |
| b69ab31 | | | 433 | $local to configure only for the current repository), by setting $mymergetool and |
| b69ab31 | | | 434 | $uimerge$brCLI merge tools like vimdiff won't be used from ISL. Ensure $gui is set to |
| b69ab31 | | | 435 | True.$br For more information, see: $help |
| b69ab31 | | | 436 | </T> |
| b69ab31 | | | 437 | </div> |
| b69ab31 | | | 438 | </div> |
| b69ab31 | | | 439 | )}> |
| b69ab31 | | | 440 | <Row> |
| b69ab31 | | | 441 | <T replace={{$cmd: cmdEl}}>External Merge Tool: $cmd</T> |
| b69ab31 | | | 442 | <Subtle> |
| b69ab31 | | | 443 | <T>How to configure?</T> |
| b69ab31 | | | 444 | </Subtle> |
| b69ab31 | | | 445 | <Icon icon="question" /> |
| b69ab31 | | | 446 | </Row> |
| b69ab31 | | | 447 | </Tooltip> |
| b69ab31 | | | 448 | ); |
| b69ab31 | | | 449 | } |
| b69ab31 | | | 450 | |
| b69ab31 | | | 451 | function ZoomUISetting() { |
| b69ab31 | | | 452 | const [zoom, setZoom] = useAtom(zoomUISettingAtom); |
| b69ab31 | | | 453 | function roundToPercent(n: number): number { |
| b69ab31 | | | 454 | return Math.round(n * 100) / 100; |
| b69ab31 | | | 455 | } |
| b69ab31 | | | 456 | return ( |
| b69ab31 | | | 457 | <div className="zoom-setting"> |
| b69ab31 | | | 458 | <Tooltip title={t('Decrease UI Zoom')}> |
| b69ab31 | | | 459 | <Button |
| b69ab31 | | | 460 | icon |
| b69ab31 | | | 461 | onClick={() => { |
| b69ab31 | | | 462 | setZoom(roundToPercent(zoom - 0.1)); |
| b69ab31 | | | 463 | }}> |
| b69ab31 | | | 464 | <Icon icon="zoom-out" /> |
| b69ab31 | | | 465 | </Button> |
| b69ab31 | | | 466 | </Tooltip> |
| b69ab31 | | | 467 | <span>{`${Math.round(100 * zoom)}%`}</span> |
| b69ab31 | | | 468 | <Tooltip title={t('Increase UI Zoom')}> |
| b69ab31 | | | 469 | <Button |
| b69ab31 | | | 470 | icon |
| b69ab31 | | | 471 | onClick={() => { |
| b69ab31 | | | 472 | setZoom(roundToPercent(zoom + 0.1)); |
| b69ab31 | | | 473 | }}> |
| b69ab31 | | | 474 | <Icon icon="zoom-in" /> |
| b69ab31 | | | 475 | </Button> |
| b69ab31 | | | 476 | </Tooltip> |
| b69ab31 | | | 477 | <div style={{width: '20px'}} /> |
| b69ab31 | | | 478 | <label> |
| b69ab31 | | | 479 | <T>Presets:</T> |
| b69ab31 | | | 480 | </label> |
| b69ab31 | | | 481 | <Button |
| b69ab31 | | | 482 | style={{fontSize: '80%'}} |
| b69ab31 | | | 483 | icon |
| b69ab31 | | | 484 | onClick={() => { |
| b69ab31 | | | 485 | setZoom(0.8); |
| b69ab31 | | | 486 | }}> |
| b69ab31 | | | 487 | <T>Small</T> |
| b69ab31 | | | 488 | </Button> |
| b69ab31 | | | 489 | <Button |
| b69ab31 | | | 490 | icon |
| b69ab31 | | | 491 | onClick={() => { |
| b69ab31 | | | 492 | setZoom(1.0); |
| b69ab31 | | | 493 | }}> |
| b69ab31 | | | 494 | <T>Normal</T> |
| b69ab31 | | | 495 | </Button> |
| b69ab31 | | | 496 | <Button |
| b69ab31 | | | 497 | style={{fontSize: '120%'}} |
| b69ab31 | | | 498 | icon |
| b69ab31 | | | 499 | onClick={() => { |
| b69ab31 | | | 500 | setZoom(1.2); |
| b69ab31 | | | 501 | }}> |
| b69ab31 | | | 502 | <T>Large</T> |
| b69ab31 | | | 503 | </Button> |
| b69ab31 | | | 504 | </div> |
| b69ab31 | | | 505 | ); |
| b69ab31 | | | 506 | } |
| b69ab31 | | | 507 | |
| b69ab31 | | | 508 | function DebugToolsField() { |
| b69ab31 | | | 509 | const [isDebug, setIsDebug] = useAtom(debugToolsEnabledState); |
| b69ab31 | | | 510 | const [overrideDisabledSubmit, setOverrideDisabledSubmit] = useAtom(overrideDisabledSubmitModes); |
| b69ab31 | | | 511 | const [debugFlag, setDebugFlag] = useAtom(enableSaplingDebugFlag); |
| b69ab31 | | | 512 | const [verboseFlag, setVerboseFlag] = useAtom(enableSaplingVerboseFlag); |
| b69ab31 | | | 513 | const provider = useAtomValue(codeReviewProvider); |
| b69ab31 | | | 514 | const commandName = useAtomValue(mainCommandName); |
| b69ab31 | | | 515 | |
| b69ab31 | | | 516 | const [branchPrsEnabled, setBranchPrsEnabled] = useAtom(experimentalBranchPRsEnabled); |
| b69ab31 | | | 517 | |
| b69ab31 | | | 518 | return ( |
| b69ab31 | | | 519 | <DropdownField title={t('Debug Tools & Experimental')}> |
| b69ab31 | | | 520 | <Column alignStart> |
| b69ab31 | | | 521 | <Checkbox |
| b69ab31 | | | 522 | checked={isDebug} |
| b69ab31 | | | 523 | onChange={checked => { |
| b69ab31 | | | 524 | setIsDebug(checked); |
| b69ab31 | | | 525 | }}> |
| b69ab31 | | | 526 | <T>Enable Debug Tools</T> |
| b69ab31 | | | 527 | </Checkbox> |
| b69ab31 | | | 528 | <ExperimentalFeaturesCheckbox /> |
| b69ab31 | | | 529 | {provider?.submitDisabledReason?.() != null && ( |
| b69ab31 | | | 530 | <Checkbox |
| b69ab31 | | | 531 | checked={overrideDisabledSubmit} |
| b69ab31 | | | 532 | onChange={setOverrideDisabledSubmit} |
| b69ab31 | | | 533 | data-testid="force-enable-github-submit"> |
| b69ab31 | | | 534 | <T>Force enable `sl pr submit` and `sl ghstack submit`</T> |
| b69ab31 | | | 535 | </Checkbox> |
| b69ab31 | | | 536 | )} |
| b69ab31 | | | 537 | {provider?.supportBranchingPrs === true && ( |
| b69ab31 | | | 538 | <Checkbox |
| b69ab31 | | | 539 | checked={branchPrsEnabled} |
| b69ab31 | | | 540 | onChange={checked => { |
| b69ab31 | | | 541 | setBranchPrsEnabled(checked); |
| b69ab31 | | | 542 | }}> |
| b69ab31 | | | 543 | <T>Enable Experimental Branching PRs for GitHub</T> |
| b69ab31 | | | 544 | </Checkbox> |
| b69ab31 | | | 545 | )} |
| b69ab31 | | | 546 | <Row> |
| b69ab31 | | | 547 | <T |
| b69ab31 | | | 548 | replace={{ |
| b69ab31 | | | 549 | $sl: <code>{commandName}</code>, |
| b69ab31 | | | 550 | $verbose: ( |
| b69ab31 | | | 551 | <Checkbox checked={verboseFlag} onChange={setVerboseFlag}> |
| b69ab31 | | | 552 | <code>--verbose</code> |
| b69ab31 | | | 553 | </Checkbox> |
| b69ab31 | | | 554 | ), |
| b69ab31 | | | 555 | $debug: ( |
| b69ab31 | | | 556 | <Checkbox checked={debugFlag} onChange={setDebugFlag}> |
| b69ab31 | | | 557 | <code>--debug</code> |
| b69ab31 | | | 558 | </Checkbox> |
| b69ab31 | | | 559 | ), |
| b69ab31 | | | 560 | }}> |
| b69ab31 | | | 561 | Pass extra flags to $sl: $verbose $debug |
| b69ab31 | | | 562 | </T> |
| b69ab31 | | | 563 | </Row> |
| b69ab31 | | | 564 | </Column> |
| b69ab31 | | | 565 | </DropdownField> |
| b69ab31 | | | 566 | ); |
| b69ab31 | | | 567 | } |
| b69ab31 | | | 568 | |
| b69ab31 | | | 569 | function ExperimentalFeaturesCheckbox() { |
| b69ab31 | | | 570 | const [experimentalFeaturesEnabled, setExperimentalFeaturesEnabled] = |
| b69ab31 | | | 571 | useAtom(hasExperimentalFeatures); |
| b69ab31 | | | 572 | |
| b69ab31 | | | 573 | if (currentExperimentalFeaturesList.length === 0) { |
| b69ab31 | | | 574 | return null; |
| b69ab31 | | | 575 | } |
| b69ab31 | | | 576 | |
| b69ab31 | | | 577 | return ( |
| b69ab31 | | | 578 | <Tooltip |
| b69ab31 | | | 579 | title={t( |
| b69ab31 | | | 580 | `Enable experimental features that are still being developed and may not work as expected. |
| b69ab31 | | | 581 | |
| b69ab31 | | | 582 | Current experimental features: ${currentExperimentalFeaturesList.join(', ')}`, |
| b69ab31 | | | 583 | )}> |
| b69ab31 | | | 584 | <Checkbox |
| b69ab31 | | | 585 | checked={experimentalFeaturesEnabled} |
| b69ab31 | | | 586 | onChange={checked => { |
| b69ab31 | | | 587 | setExperimentalFeaturesEnabled(checked); |
| b69ab31 | | | 588 | }}> |
| b69ab31 | | | 589 | <T>Enable Experimental Features</T> |
| b69ab31 | | | 590 | </Checkbox> |
| b69ab31 | | | 591 | </Tooltip> |
| b69ab31 | | | 592 | ); |
| b69ab31 | | | 593 | } |