22 import org.gjt.sp.jedit.textarea.{JEditTextArea, TextArea, Selection} |
22 import org.gjt.sp.jedit.textarea.{JEditTextArea, TextArea, Selection} |
23 import org.gjt.sp.jedit.gui.{HistoryTextField, KeyEventWorkaround} |
23 import org.gjt.sp.jedit.gui.{HistoryTextField, KeyEventWorkaround} |
24 import org.gjt.sp.util.StandardUtilities |
24 import org.gjt.sp.util.StandardUtilities |
25 |
25 |
26 |
26 |
27 object Completion_Popup |
27 object Completion_Popup { |
28 { |
|
29 /** items with HTML rendering **/ |
28 /** items with HTML rendering **/ |
30 |
29 |
31 private class Item(val item: Completion.Item) |
30 private class Item(val item: Completion.Item) { |
32 { |
|
33 private val html = |
31 private val html = |
34 item.description match { |
32 item.description match { |
35 case a :: bs => |
33 case a :: bs => |
36 "<html><b>" + HTML.output(Symbol.print_newlines(a)) + "</b>" + |
34 "<html><b>" + HTML.output(Symbol.print_newlines(a)) + "</b>" + |
37 HTML.output(bs.map(b => " " + Symbol.print_newlines(b)).mkString) + "</html>" |
35 HTML.output(bs.map(b => " " + Symbol.print_newlines(b)).mkString) + "</html>" |
72 text_area_completion.action(immediate = true, explicit = true, word_only = word_only) |
68 text_area_completion.action(immediate = true, explicit = true, word_only = word_only) |
73 true |
69 true |
74 case None => false |
70 case None => false |
75 } |
71 } |
76 |
72 |
77 def exit(text_area: JEditTextArea): Unit = |
73 def exit(text_area: JEditTextArea): Unit = { |
78 { |
|
79 GUI_Thread.require {} |
74 GUI_Thread.require {} |
80 apply(text_area) match { |
75 apply(text_area) match { |
81 case None => |
76 case None => |
82 case Some(text_area_completion) => |
77 case Some(text_area_completion) => |
83 text_area_completion.deactivate() |
78 text_area_completion.deactivate() |
84 text_area.putClientProperty(key, null) |
79 text_area.putClientProperty(key, null) |
85 } |
80 } |
86 } |
81 } |
87 |
82 |
88 def init(text_area: JEditTextArea): Completion_Popup.Text_Area = |
83 def init(text_area: JEditTextArea): Completion_Popup.Text_Area = { |
89 { |
|
90 exit(text_area) |
84 exit(text_area) |
91 val text_area_completion = new Text_Area(text_area) |
85 val text_area_completion = new Text_Area(text_area) |
92 text_area.putClientProperty(key, text_area_completion) |
86 text_area.putClientProperty(key, text_area_completion) |
93 text_area_completion.activate() |
87 text_area_completion.activate() |
94 text_area_completion |
88 text_area_completion |
95 } |
89 } |
96 |
90 |
97 def dismissed(text_area: TextArea): Boolean = |
91 def dismissed(text_area: TextArea): Boolean = { |
98 { |
|
99 GUI_Thread.require {} |
92 GUI_Thread.require {} |
100 apply(text_area) match { |
93 apply(text_area) match { |
101 case Some(text_area_completion) => text_area_completion.dismissed() |
94 case Some(text_area_completion) => text_area_completion.dismissed() |
102 case None => false |
95 case None => false |
103 } |
96 } |
104 } |
97 } |
105 } |
98 } |
106 |
99 |
107 class Text_Area private(text_area: JEditTextArea) |
100 class Text_Area private(text_area: JEditTextArea) { |
108 { |
|
109 // owned by GUI thread |
101 // owned by GUI thread |
110 private var completion_popup: Option[Completion_Popup] = None |
102 private var completion_popup: Option[Completion_Popup] = None |
111 |
103 |
112 def active_range: Option[Text.Range] = |
104 def active_range: Option[Text.Range] = |
113 completion_popup match { |
105 completion_popup match { |
149 /* syntax completion */ |
140 /* syntax completion */ |
150 |
141 |
151 def syntax_completion( |
142 def syntax_completion( |
152 history: Completion.History, |
143 history: Completion.History, |
153 explicit: Boolean, |
144 explicit: Boolean, |
154 opt_rendering: Option[JEdit_Rendering]): Option[Completion.Result] = |
145 opt_rendering: Option[JEdit_Rendering] |
155 { |
146 ): Option[Completion.Result] = { |
156 val buffer = text_area.getBuffer |
147 val buffer = text_area.getBuffer |
157 val unicode = Isabelle_Encoding.is_active(buffer) |
148 val unicode = Isabelle_Encoding.is_active(buffer) |
158 |
149 |
159 Isabelle.buffer_syntax(buffer) match { |
150 Isabelle.buffer_syntax(buffer) match { |
160 case Some(syntax) => |
151 case Some(syntax) => |
227 |
217 |
228 def action( |
218 def action( |
229 immediate: Boolean = false, |
219 immediate: Boolean = false, |
230 explicit: Boolean = false, |
220 explicit: Boolean = false, |
231 delayed: Boolean = false, |
221 delayed: Boolean = false, |
232 word_only: Boolean = false): Boolean = |
222 word_only: Boolean = false |
233 { |
223 ): Boolean = { |
234 val view = text_area.getView |
224 val view = text_area.getView |
235 val layered = view.getLayeredPane |
225 val layered = view.getLayeredPane |
236 val buffer = text_area.getBuffer |
226 val buffer = text_area.getBuffer |
237 val painter = text_area.getPainter |
227 val painter = text_area.getPainter |
238 |
228 |
239 val history = PIDE.plugin.completion_history.value |
229 val history = PIDE.plugin.completion_history.value |
240 val unicode = Isabelle_Encoding.is_active(buffer) |
230 val unicode = Isabelle_Encoding.is_active(buffer) |
241 |
231 |
242 def open_popup(result: Completion.Result): Unit = |
232 def open_popup(result: Completion.Result): Unit = { |
243 { |
|
244 val font = |
233 val font = |
245 painter.getFont.deriveFont( |
234 painter.getFont.deriveFont( |
246 Font_Info.main_size(PIDE.options.real("jedit_popup_font_scale"))) |
235 Font_Info.main_size(PIDE.options.real("jedit_popup_font_scale"))) |
247 |
236 |
248 val range = result.range |
237 val range = result.range |
253 SwingUtilities.convertPoint(painter, |
242 SwingUtilities.convertPoint(painter, |
254 loc1.x, loc1.y + painter.getLineHeight, layered) |
243 loc1.x, loc1.y + painter.getLineHeight, layered) |
255 |
244 |
256 val items = result.items.map(new Item(_)) |
245 val items = result.items.map(new Item(_)) |
257 val completion = |
246 val completion = |
258 new Completion_Popup(Some(range), layered, loc2, font, items) |
247 new Completion_Popup(Some(range), layered, loc2, font, items) { |
259 { |
248 override def complete(item: Completion.Item): Unit = { |
260 override def complete(item: Completion.Item): Unit = |
|
261 { |
|
262 PIDE.plugin.completion_history.update(item) |
249 PIDE.plugin.completion_history.update(item) |
263 insert(item) |
250 insert(item) |
264 } |
251 } |
265 override def propagate(evt: KeyEvent): Unit = |
252 override def propagate(evt: KeyEvent): Unit = { |
266 { |
|
267 if (view.getKeyEventInterceptor == null) |
253 if (view.getKeyEventInterceptor == null) |
268 JEdit_Lib.propagate_key(view, evt) |
254 JEdit_Lib.propagate_key(view, evt) |
269 else if (view.getKeyEventInterceptor == inner_key_listener) { |
255 else if (view.getKeyEventInterceptor == inner_key_listener) { |
270 try { |
256 try { |
271 view.setKeyEventInterceptor(null) |
257 view.setKeyEventInterceptor(null) |
275 if (isDisplayable) view.setKeyEventInterceptor(inner_key_listener) |
261 if (isDisplayable) view.setKeyEventInterceptor(inner_key_listener) |
276 } |
262 } |
277 } |
263 } |
278 if (evt.getID == KeyEvent.KEY_TYPED) input(evt) |
264 if (evt.getID == KeyEvent.KEY_TYPED) input(evt) |
279 } |
265 } |
280 override def shutdown(focus: Boolean): Unit = |
266 override def shutdown(focus: Boolean): Unit = { |
281 { |
|
282 if (view.getKeyEventInterceptor == inner_key_listener) |
267 if (view.getKeyEventInterceptor == inner_key_listener) |
283 view.setKeyEventInterceptor(null) |
268 view.setKeyEventInterceptor(null) |
284 if (focus) text_area.requestFocus() |
269 if (focus) text_area.requestFocus() |
285 JEdit_Lib.invalidate_range(text_area, range) |
270 JEdit_Lib.invalidate_range(text_area, range) |
286 } |
271 } |
296 |
281 |
297 if (buffer.isEditable) { |
282 if (buffer.isEditable) { |
298 val caret = text_area.getCaretPosition |
283 val caret = text_area.getCaretPosition |
299 val opt_rendering = Document_View.get(text_area).map(_.get_rendering()) |
284 val opt_rendering = Document_View.get(text_area).map(_.get_rendering()) |
300 val result0 = syntax_completion(history, explicit, opt_rendering) |
285 val result0 = syntax_completion(history, explicit, opt_rendering) |
301 val (no_completion, semantic_completion) = |
286 val (no_completion, semantic_completion) = { |
302 { |
|
303 opt_rendering match { |
287 opt_rendering match { |
304 case Some(rendering) => |
288 case Some(rendering) => |
305 rendering.semantic_completion_result(history, unicode, result0.map(_.range), |
289 rendering.semantic_completion_result(history, unicode, result0.map(_.range), |
306 rendering.before_caret_range(caret)) |
290 rendering.before_caret_range(caret)) |
307 case None => (false, None) |
291 case None => (false, None) |
308 } |
292 } |
309 } |
293 } |
310 if (no_completion) false |
294 if (no_completion) false |
311 else { |
295 else { |
312 val result = |
296 val result = { |
313 { |
|
314 val result1 = |
297 val result1 = |
315 if (word_only) None |
298 if (word_only) None |
316 else Completion.Result.merge(history, semantic_completion, result0) |
299 else Completion.Result.merge(history, semantic_completion, result0) |
317 opt_rendering match { |
300 opt_rendering match { |
318 case None => result1 |
301 case None => result1 |
420 class History_Text_Field( |
400 class History_Text_Field( |
421 name: String = null, |
401 name: String = null, |
422 instant_popups: Boolean = false, |
402 instant_popups: Boolean = false, |
423 enter_adds_to_history: Boolean = true, |
403 enter_adds_to_history: Boolean = true, |
424 syntax: Outer_Syntax = Outer_Syntax.empty) extends |
404 syntax: Outer_Syntax = Outer_Syntax.empty) extends |
425 HistoryTextField(name, instant_popups, enter_adds_to_history) |
405 HistoryTextField(name, instant_popups, enter_adds_to_history |
426 { |
406 ) { |
427 text_field => |
407 text_field => |
428 |
408 |
429 // see https://forums.oracle.com/thread/1361677 |
409 // see https://forums.oracle.com/thread/1361677 |
430 if (GUI.is_macos_laf) text_field.setCaret(new DefaultCaret) |
410 if (GUI.is_macos_laf) text_field.setCaret(new DefaultCaret) |
431 |
411 |
489 val loc = |
466 val loc = |
490 SwingUtilities.convertPoint(text_field, fm.stringWidth(text), fm.getHeight, layered) |
467 SwingUtilities.convertPoint(text_field, fm.stringWidth(text), fm.getHeight, layered) |
491 |
468 |
492 val items = result.items.map(new Item(_)) |
469 val items = result.items.map(new Item(_)) |
493 val completion = |
470 val completion = |
494 new Completion_Popup(None, layered, loc, text_field.getFont, items) |
471 new Completion_Popup(None, layered, loc, text_field.getFont, items) { |
495 { |
472 override def complete(item: Completion.Item): Unit = { |
496 override def complete(item: Completion.Item): Unit = |
|
497 { |
|
498 PIDE.plugin.completion_history.update(item) |
473 PIDE.plugin.completion_history.update(item) |
499 insert(item) |
474 insert(item) |
500 } |
475 } |
501 override def propagate(evt: KeyEvent): Unit = |
476 override def propagate(evt: KeyEvent): Unit = |
502 if (!evt.isConsumed) text_field.processKeyEvent(evt) |
477 if (!evt.isConsumed) text_field.processKeyEvent(evt) |
562 class Completion_Popup private( |
535 class Completion_Popup private( |
563 opt_range: Option[Text.Range], |
536 opt_range: Option[Text.Range], |
564 layered: JLayeredPane, |
537 layered: JLayeredPane, |
565 location: Point, |
538 location: Point, |
566 font: Font, |
539 font: Font, |
567 items: List[Completion_Popup.Item]) extends JPanel(new BorderLayout) |
540 items: List[Completion_Popup.Item] |
568 { |
541 ) extends JPanel(new BorderLayout) { |
569 completion => |
542 completion => |
570 |
543 |
571 GUI_Thread.require {} |
544 GUI_Thread.require {} |
572 |
545 |
573 require(items.nonEmpty, "no completion items") |
546 require(items.nonEmpty, "no completion items") |
593 for (cond <- |
566 for (cond <- |
594 List(JComponent.WHEN_FOCUSED, |
567 List(JComponent.WHEN_FOCUSED, |
595 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, |
568 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, |
596 JComponent.WHEN_IN_FOCUSED_WINDOW)) list_view.peer.setInputMap(cond, null) |
569 JComponent.WHEN_IN_FOCUSED_WINDOW)) list_view.peer.setInputMap(cond, null) |
597 |
570 |
598 private def complete_selected(): Boolean = |
571 private def complete_selected(): Boolean = { |
599 { |
|
600 list_view.selection.items.toList match { |
572 list_view.selection.items.toList match { |
601 case item :: _ => complete(item.item); true |
573 case item :: _ => complete(item.item); true |
602 case _ => false |
574 case _ => false |
603 } |
575 } |
604 } |
576 } |
605 |
577 |
606 private def move_items(n: Int): Unit = |
578 private def move_items(n: Int): Unit = { |
607 { |
|
608 val i = list_view.peer.getSelectedIndex |
579 val i = list_view.peer.getSelectedIndex |
609 val j = ((i + n) min (items.length - 1)) max 0 |
580 val j = ((i + n) min (items.length - 1)) max 0 |
610 if (i != j) { |
581 if (i != j) { |
611 list_view.peer.setSelectedIndex(j) |
582 list_view.peer.setSelectedIndex(j) |
612 list_view.peer.ensureIndexIsVisible(j) |
583 list_view.peer.ensureIndexIsVisible(j) |
613 } |
584 } |
614 } |
585 } |
615 |
586 |
616 private def move_pages(n: Int): Unit = |
587 private def move_pages(n: Int): Unit = { |
617 { |
|
618 val page_size = list_view.peer.getVisibleRowCount - 1 |
588 val page_size = list_view.peer.getVisibleRowCount - 1 |
619 move_items(page_size * n) |
589 move_items(page_size * n) |
620 } |
590 } |
621 |
591 |
622 |
592 |
623 /* event handling */ |
593 /* event handling */ |
624 |
594 |
625 val inner_key_listener: KeyListener = |
595 val inner_key_listener: KeyListener = |
626 JEdit_Lib.key_listener( |
596 JEdit_Lib.key_listener( |
627 key_pressed = (e: KeyEvent) => |
597 key_pressed = (e: KeyEvent) => { |
628 { |
598 if (!e.isConsumed) { |
629 if (!e.isConsumed) { |
599 e.getKeyCode match { |
630 e.getKeyCode match { |
600 case KeyEvent.VK_ENTER if PIDE.options.bool("jedit_completion_select_enter") => |
631 case KeyEvent.VK_ENTER if PIDE.options.bool("jedit_completion_select_enter") => |
601 if (complete_selected()) e.consume |
632 if (complete_selected()) e.consume |
602 hide_popup() |
|
603 case KeyEvent.VK_TAB if PIDE.options.bool("jedit_completion_select_tab") => |
|
604 if (complete_selected()) e.consume |
|
605 hide_popup() |
|
606 case KeyEvent.VK_ESCAPE => |
|
607 hide_popup() |
|
608 e.consume |
|
609 case KeyEvent.VK_UP | KeyEvent.VK_KP_UP if multi => |
|
610 move_items(-1) |
|
611 e.consume |
|
612 case KeyEvent.VK_DOWN | KeyEvent.VK_KP_DOWN if multi => |
|
613 move_items(1) |
|
614 e.consume |
|
615 case KeyEvent.VK_PAGE_UP if multi => |
|
616 move_pages(-1) |
|
617 e.consume |
|
618 case KeyEvent.VK_PAGE_DOWN if multi => |
|
619 move_pages(1) |
|
620 e.consume |
|
621 case _ => |
|
622 if (e.isActionKey || e.isAltDown || e.isMetaDown || e.isControlDown) |
633 hide_popup() |
623 hide_popup() |
634 case KeyEvent.VK_TAB if PIDE.options.bool("jedit_completion_select_tab") => |
624 } |
635 if (complete_selected()) e.consume |
625 } |
636 hide_popup() |
626 propagate(e) |
637 case KeyEvent.VK_ESCAPE => |
627 }, |
638 hide_popup() |
|
639 e.consume |
|
640 case KeyEvent.VK_UP | KeyEvent.VK_KP_UP if multi => |
|
641 move_items(-1) |
|
642 e.consume |
|
643 case KeyEvent.VK_DOWN | KeyEvent.VK_KP_DOWN if multi => |
|
644 move_items(1) |
|
645 e.consume |
|
646 case KeyEvent.VK_PAGE_UP if multi => |
|
647 move_pages(-1) |
|
648 e.consume |
|
649 case KeyEvent.VK_PAGE_DOWN if multi => |
|
650 move_pages(1) |
|
651 e.consume |
|
652 case _ => |
|
653 if (e.isActionKey || e.isAltDown || e.isMetaDown || e.isControlDown) |
|
654 hide_popup() |
|
655 } |
|
656 } |
|
657 propagate(e) |
|
658 }, |
|
659 key_typed = propagate |
628 key_typed = propagate |
660 ) |
629 ) |
661 |
630 |
662 list_view.peer.addKeyListener(inner_key_listener) |
631 list_view.peer.addKeyListener(inner_key_listener) |
663 |
632 |
664 list_view.peer.addMouseListener(new MouseAdapter { |
633 list_view.peer.addMouseListener(new MouseAdapter { |
665 override def mouseClicked(e: MouseEvent): Unit = |
634 override def mouseClicked(e: MouseEvent): Unit = { |
666 { |
|
667 if (complete_selected()) e.consume |
635 if (complete_selected()) e.consume |
668 hide_popup() |
636 hide_popup() |
669 } |
637 } |
670 }) |
638 }) |
671 |
639 |
684 /* popup */ |
652 /* popup */ |
685 |
653 |
686 def active_range: Option[Text.Range] = |
654 def active_range: Option[Text.Range] = |
687 if (isDisplayable) opt_range else None |
655 if (isDisplayable) opt_range else None |
688 |
656 |
689 private val popup = |
657 private val popup = { |
690 { |
|
691 val screen = GUI.screen_location(layered, location) |
658 val screen = GUI.screen_location(layered, location) |
692 val size = |
659 val size = { |
693 { |
|
694 val geometry = JEdit_Lib.window_geometry(completion, completion) |
660 val geometry = JEdit_Lib.window_geometry(completion, completion) |
695 val bounds = JEdit_Rendering.popup_bounds |
661 val bounds = JEdit_Rendering.popup_bounds |
696 val w = geometry.width min (screen.bounds.width * bounds).toInt min layered.getWidth |
662 val w = geometry.width min (screen.bounds.width * bounds).toInt min layered.getWidth |
697 val h = geometry.height min (screen.bounds.height * bounds).toInt min layered.getHeight |
663 val h = geometry.height min (screen.bounds.height * bounds).toInt min layered.getHeight |
698 new Dimension(w, h) |
664 new Dimension(w, h) |
699 } |
665 } |
700 new Popup(layered, completion, screen.relative(layered, size), size) |
666 new Popup(layered, completion, screen.relative(layered, size), size) |
701 } |
667 } |
702 |
668 |
703 private def show_popup(focus: Boolean): Unit = |
669 private def show_popup(focus: Boolean): Unit = { |
704 { |
|
705 popup.show |
670 popup.show |
706 if (focus) list_view.requestFocus() |
671 if (focus) list_view.requestFocus() |
707 } |
672 } |
708 |
673 |
709 private def hide_popup(): Unit = |
674 private def hide_popup(): Unit = { |
710 { |
|
711 shutdown(list_view.peer.isFocusOwner) |
675 shutdown(list_view.peer.isFocusOwner) |
712 popup.hide |
676 popup.hide |
713 } |
677 } |
714 } |
678 } |