Skip to content

Commit ada1289

Browse files
committed
Support nearby text cursor when completion panel is not visible
1 parent e5f5600 commit ada1289

3 files changed

Lines changed: 285 additions & 91 deletions

File tree

Core/Sources/SuggestionWidget/Styles.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import SwiftUI
55
enum Style {
66
static let panelHeight: Double = 500
77
static let panelWidth: Double = 454
8+
static let inlineSuggestionMinWidth: Double = 540
89
static let widgetHeight: Double = 24
910
static var widgetWidth: Double { widgetHeight }
1011
static let widgetPadding: Double = 4

Core/Sources/SuggestionWidget/SuggestionWidgetController.swift

Lines changed: 129 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -476,80 +476,32 @@ extension SuggestionWidgetController {
476476
private func updateWindowLocation(animated: Bool = false) {
477477
let detachChat = chatWindowViewModel.chatPanelInASeparateWindow
478478

479-
if let widgetFrames = {
480-
if let application = XcodeInspector.shared.latestActiveXcode?.appElement {
481-
if let focusElement = application.focusedElement,
482-
focusElement.description == "Source Editor",
483-
let parent = focusElement.parent,
484-
let frame = parent.rect,
485-
let screen = NSScreen.screens.first(where: { $0.frame.origin == .zero }),
486-
let firstScreen = NSScreen.main
487-
{
488-
let mode = UserDefaults.shared.value(for: \.suggestionWidgetPositionMode)
489-
switch mode {
490-
case .fixedToBottom:
491-
return UpdateLocationStrategy.FixedToBottom().framesForWindows(
492-
editorFrame: frame,
493-
mainScreen: screen,
494-
activeScreen: firstScreen
495-
)
496-
case .alignToTextCursor:
497-
return UpdateLocationStrategy.AlignToTextCursor().framesForWindows(
498-
editorFrame: frame,
499-
mainScreen: screen,
500-
activeScreen: firstScreen,
501-
editor: focusElement
502-
)
503-
}
504-
} else if var window = application.focusedWindow,
505-
var frame = application.focusedWindow?.rect,
506-
!["menu bar", "menu bar item"].contains(window.description),
507-
frame.size.height > 300,
508-
let screen = NSScreen.screens.first(where: { $0.frame.origin == .zero }),
509-
let firstScreen = NSScreen.main
510-
{
511-
if ["open_quickly"].contains(window.identifier)
512-
|| ["alert"].contains(window.label)
513-
{
514-
// fallback to use workspace window
515-
guard let workspaceWindow = application.windows
516-
.first(where: { $0.identifier == "Xcode.WorkspaceWindow" }),
517-
let rect = workspaceWindow.rect
518-
else { return (.zero, .zero, .zero, false) }
519-
520-
window = workspaceWindow
521-
frame = rect
522-
}
523-
524-
if ["Xcode.WorkspaceWindow"].contains(window.identifier) {
525-
// extra padding to bottom so buttons won't be covered
526-
frame.size.height -= 40
527-
} else {
528-
// move a bit away from the window so buttons won't be covered
529-
frame.origin.x -= Style.widgetPadding + Style.widgetWidth / 2
530-
frame.size.width += Style.widgetPadding * 2 + Style.widgetWidth
531-
}
532-
533-
return UpdateLocationStrategy.FixedToBottom().framesForWindows(
534-
editorFrame: frame,
535-
mainScreen: screen,
536-
activeScreen: firstScreen,
537-
preferredInsideEditorMinWidth: 9_999_999_999 // never
538-
)
539-
}
540-
}
541-
return nil
542-
}() {
543-
widgetWindow.setFrame(widgetFrames.widgetFrame, display: false, animate: animated)
544-
panelWindow.setFrame(widgetFrames.panelFrame, display: false, animate: animated)
545-
tabWindow.setFrame(widgetFrames.tabFrame, display: false, animate: animated)
546-
suggestionPanelViewModel.alignTopToAnchor = widgetFrames.alignPanelTopToAnchor
479+
if let widgetLocation = generateWidgetLocation() {
480+
widgetWindow.setFrame(widgetLocation.widgetFrame, display: false, animate: animated)
481+
tabWindow.setFrame(widgetLocation.tabFrame, display: false, animate: animated)
482+
panelWindow.setFrame(
483+
(widgetLocation.suggestionPanelLocation ?? widgetLocation.defaultPanelLocation)
484+
.frame,
485+
display: false,
486+
animate: animated
487+
)
488+
suggestionPanelViewModel.alignTopToAnchor = (
489+
widgetLocation.suggestionPanelLocation ?? widgetLocation.defaultPanelLocation
490+
).alignPanelTop
547491
if detachChat {
548492
if chatWindow.alphaValue == 0 {
549-
chatWindow.setFrame(panelWindow.frame, display: false, animate: animated)
493+
chatWindow.setFrame(
494+
widgetLocation.defaultPanelLocation.frame,
495+
display: false,
496+
animate: animated
497+
)
550498
}
551499
} else {
552-
chatWindow.setFrame(panelWindow.frame, display: false, animate: animated)
500+
chatWindow.setFrame(
501+
widgetLocation.defaultPanelLocation.frame,
502+
display: false,
503+
animate: animated
504+
)
553505
}
554506
}
555507

@@ -622,8 +574,113 @@ extension SuggestionWidgetController {
622574
suggestionPanelViewModel.content = nil
623575
}
624576
}
577+
578+
private func generateWidgetLocation() -> WidgetLocation? {
579+
if let application = XcodeInspector.shared.latestActiveXcode?.appElement {
580+
if let focusElement = application.focusedElement,
581+
focusElement.description == "Source Editor",
582+
let parent = focusElement.parent,
583+
let frame = parent.rect,
584+
let screen = NSScreen.screens.first(where: { $0.frame.origin == .zero }),
585+
let firstScreen = NSScreen.main
586+
{
587+
let positionMode = UserDefaults.shared
588+
.value(for: \.suggestionWidgetPositionMode)
589+
let suggestionMode = UserDefaults.shared
590+
.value(for: \.suggestionPresentationMode)
591+
592+
switch positionMode {
593+
case .fixedToBottom:
594+
var result = UpdateLocationStrategy.FixedToBottom().framesForWindows(
595+
editorFrame: frame,
596+
mainScreen: screen,
597+
activeScreen: firstScreen
598+
)
599+
switch suggestionMode {
600+
case .nearbyTextCursor:
601+
result.suggestionPanelLocation = UpdateLocationStrategy
602+
.NearbyTextCursor()
603+
.framesForSuggestionWindow(
604+
editorFrame: frame, mainScreen: screen,
605+
activeScreen: firstScreen,
606+
editor: focusElement,
607+
completionPanel: XcodeInspector.shared.completionPanel
608+
)
609+
default:
610+
break
611+
}
612+
return result
613+
case .alignToTextCursor:
614+
var result = UpdateLocationStrategy.AlignToTextCursor().framesForWindows(
615+
editorFrame: frame,
616+
mainScreen: screen,
617+
activeScreen: firstScreen,
618+
editor: focusElement
619+
)
620+
switch suggestionMode {
621+
case .nearbyTextCursor:
622+
result.suggestionPanelLocation = UpdateLocationStrategy
623+
.NearbyTextCursor()
624+
.framesForSuggestionWindow(
625+
editorFrame: frame, mainScreen: screen,
626+
activeScreen: firstScreen,
627+
editor: focusElement,
628+
completionPanel: XcodeInspector.shared.completionPanel
629+
)
630+
default:
631+
break
632+
}
633+
return result
634+
}
635+
} else if var window = application.focusedWindow,
636+
var frame = application.focusedWindow?.rect,
637+
!["menu bar", "menu bar item"].contains(window.description),
638+
frame.size.height > 300,
639+
let screen = NSScreen.screens.first(where: { $0.frame.origin == .zero }),
640+
let firstScreen = NSScreen.main
641+
{
642+
if ["open_quickly"].contains(window.identifier)
643+
|| ["alert"].contains(window.label)
644+
{
645+
// fallback to use workspace window
646+
guard let workspaceWindow = application.windows
647+
.first(where: { $0.identifier == "Xcode.WorkspaceWindow" }),
648+
let rect = workspaceWindow.rect
649+
else {
650+
return WidgetLocation(
651+
widgetFrame: .zero,
652+
tabFrame: .zero,
653+
defaultPanelLocation: .init(frame: .zero, alignPanelTop: false)
654+
)
655+
}
656+
657+
window = workspaceWindow
658+
frame = rect
659+
}
660+
661+
if ["Xcode.WorkspaceWindow"].contains(window.identifier) {
662+
// extra padding to bottom so buttons won't be covered
663+
frame.size.height -= 40
664+
} else {
665+
// move a bit away from the window so buttons won't be covered
666+
frame.origin.x -= Style.widgetPadding + Style.widgetWidth / 2
667+
frame.size.width += Style.widgetPadding * 2 + Style.widgetWidth
668+
}
669+
670+
return UpdateLocationStrategy.FixedToBottom().framesForWindows(
671+
editorFrame: frame,
672+
mainScreen: screen,
673+
activeScreen: firstScreen,
674+
preferredInsideEditorMinWidth: 9_999_999_999 // never
675+
)
676+
}
677+
}
678+
return nil
679+
}
625680
}
626681

682+
// MARK: - NSWindowDelegate
683+
627684
extension SuggestionWidgetController: NSWindowDelegate {
628685
public func windowWillMove(_ notification: Notification) {
629686
guard (notification.object as? NSWindow) === chatWindow else { return }
@@ -659,6 +716,8 @@ extension SuggestionWidgetController: NSWindowDelegate {
659716
}
660717
}
661718

719+
// MARK: - Window Subclasses
720+
662721
class CanBecomeKeyWindow: NSWindow {
663722
var canBecomeKeyChecker: () -> Bool = { true }
664723
override var canBecomeKey: Bool { canBecomeKeyChecker() }

0 commit comments

Comments
 (0)