History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/text/DefaultFormatter.java (1 of 18352 files). Show all

Unified · Split

java/pkg/javax/swing/text/DefaultFormatter.java+760−0
@@ -0,0 +1,760 @@
1/*
2 * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package javax.swing.text;
26
27import java.io.Serializable;
28import java.lang.reflect.*;
29import java.text.ParseException;
30import javax.swing.*;
31import javax.swing.text.*;
32
33/**
34 * <code>DefaultFormatter</code> formats aribtrary objects. Formatting is done
35 * by invoking the <code>toString</code> method. In order to convert the
36 * value back to a String, your class must provide a constructor that
37 * takes a String argument. If no single argument constructor that takes a
38 * String is found, the returned value will be the String passed into
39 * <code>stringToValue</code>.
40 * <p>
41 * Instances of <code>DefaultFormatter</code> can not be used in multiple
42 * instances of <code>JFormattedTextField</code>. To obtain a copy of
43 * an already configured <code>DefaultFormatter</code>, use the
44 * <code>clone</code> method.
45 * <p>
46 * <strong>Warning:</strong>
47 * Serialized objects of this class will not be compatible with
48 * future Swing releases. The current serialization support is
49 * appropriate for short term storage or RMI between applications running
50 * the same version of Swing. As of 1.4, support for long term storage
51 * of all JavaBeans<sup><font size="-2">TM</font></sup>
52 * has been added to the <code>java.beans</code> package.
53 * Please see {@link java.beans.XMLEncoder}.
54 *
55 * @see javax.swing.JFormattedTextField.AbstractFormatter
56 *
57 * @since 1.4
58 */
59public class DefaultFormatter extends JFormattedTextField.AbstractFormatter
60 implements Cloneable, Serializable {
61 /** Indicates if the value being edited must match the mask. */
62 private boolean allowsInvalid;
63
64 /** If true, editing mode is in overwrite (or strikethough). */
65 private boolean overwriteMode;
66
67 /** If true, any time a valid edit happens commitEdit is invoked. */
68 private boolean commitOnEdit;
69
70 /** Class used to create new instances. */
71 private Class<?> valueClass;
72
73 /** NavigationFilter that forwards calls back to DefaultFormatter. */
74 private NavigationFilter navigationFilter;
75
76 /** DocumentFilter that forwards calls back to DefaultFormatter. */
77 private DocumentFilter documentFilter;
78
79 /** Used during replace to track the region to replace. */
80 transient ReplaceHolder replaceHolder;
81
82
83 /**
84 * Creates a DefaultFormatter.
85 */
86 public DefaultFormatter() {
87 overwriteMode = true;
88 allowsInvalid = true;
89 }
90
91 /**
92 * Installs the <code>DefaultFormatter</code> onto a particular
93 * <code>JFormattedTextField</code>.
94 * This will invoke <code>valueToString</code> to convert the
95 * current value from the <code>JFormattedTextField</code> to
96 * a String. This will then install the <code>Action</code>s from
97 * <code>getActions</code>, the <code>DocumentFilter</code>
98 * returned from <code>getDocumentFilter</code> and the
99 * <code>NavigationFilter</code> returned from
100 * <code>getNavigationFilter</code> onto the
101 * <code>JFormattedTextField</code>.
102 * <p>
103 * Subclasses will typically only need to override this if they
104 * wish to install additional listeners on the
105 * <code>JFormattedTextField</code>.
106 * <p>
107 * If there is a <code>ParseException</code> in converting the
108 * current value to a String, this will set the text to an empty
109 * String, and mark the <code>JFormattedTextField</code> as being
110 * in an invalid state.
111 * <p>
112 * While this is a public method, this is typically only useful
113 * for subclassers of <code>JFormattedTextField</code>.
114 * <code>JFormattedTextField</code> will invoke this method at
115 * the appropriate times when the value changes, or its internal
116 * state changes.
117 *
118 * @param ftf JFormattedTextField to format for, may be null indicating
119 * uninstall from current JFormattedTextField.
120 */
121 public void install(JFormattedTextField ftf) {
122 super.install(ftf);
123 positionCursorAtInitialLocation();
124 }
125
126 /**
127 * Sets when edits are published back to the
128 * <code>JFormattedTextField</code>. If true, <code>commitEdit</code>
129 * is invoked after every valid edit (any time the text is edited). On
130 * the other hand, if this is false than the <code>DefaultFormatter</code>
131 * does not publish edits back to the <code>JFormattedTextField</code>.
132 * As such, the only time the value of the <code>JFormattedTextField</code>
133 * will change is when <code>commitEdit</code> is invoked on
134 * <code>JFormattedTextField</code>, typically when enter is pressed
135 * or focus leaves the <code>JFormattedTextField</code>.
136 *
137 * @param commit Used to indicate when edits are commited back to the
138 * JTextComponent
139 */
140 public void setCommitsOnValidEdit(boolean commit) {
141 commitOnEdit = commit;
142 }
143
144 /**
145 * Returns when edits are published back to the
146 * <code>JFormattedTextField</code>.
147 *
148 * @return true if edits are commited after evey valid edit
149 */
150 public boolean getCommitsOnValidEdit() {
151 return commitOnEdit;
152 }
153
154 /**
155 * Configures the behavior when inserting characters. If
156 * <code>overwriteMode</code> is true (the default), new characters
157 * overwrite existing characters in the model.
158 *
159 * @param overwriteMode Indicates if overwrite or overstrike mode is used
160 */
161 public void setOverwriteMode(boolean overwriteMode) {
162 this.overwriteMode = overwriteMode;
163 }
164
165 /**
166 * Returns the behavior when inserting characters.
167 *
168 * @return true if newly inserted characters overwrite existing characters
169 */
170 public boolean getOverwriteMode() {
171 return overwriteMode;
172 }
173
174 /**
175 * Sets whether or not the value being edited is allowed to be invalid
176 * for a length of time (that is, <code>stringToValue</code> throws
177 * a <code>ParseException</code>).
178 * It is often convenient to allow the user to temporarily input an
179 * invalid value.
180 *
181 * @param allowsInvalid Used to indicate if the edited value must always
182 * be valid
183 */
184 public void setAllowsInvalid(boolean allowsInvalid) {
185 this.allowsInvalid = allowsInvalid;
186 }
187
188 /**
189 * Returns whether or not the value being edited is allowed to be invalid
190 * for a length of time.
191 *
192 * @return false if the edited value must always be valid
193 */
194 public boolean getAllowsInvalid() {
195 return allowsInvalid;
196 }
197
198 /**
199 * Sets that class that is used to create new Objects. If the
200 * passed in class does not have a single argument constructor that
201 * takes a String, String values will be used.
202 *
203 * @param valueClass Class used to construct return value from
204 * stringToValue
205 */
206 public void setValueClass(Class<?> valueClass) {
207 this.valueClass = valueClass;
208 }
209
210 /**
211 * Returns that class that is used to create new Objects.
212 *
213 * @return Class used to constuct return value from stringToValue
214 */
215 public Class<?> getValueClass() {
216 return valueClass;
217 }
218
219 /**
220 * Converts the passed in String into an instance of
221 * <code>getValueClass</code> by way of the constructor that
222 * takes a String argument. If <code>getValueClass</code>
223 * returns null, the Class of the current value in the
224 * <code>JFormattedTextField</code> will be used. If this is null, a
225 * String will be returned. If the constructor thows an exception, a
226 * <code>ParseException</code> will be thrown. If there is no single
227 * argument String constructor, <code>string</code> will be returned.
228 *
229 * @throws ParseException if there is an error in the conversion
230 * @param string String to convert
231 * @return Object representation of text
232 */
233 public Object stringToValue(String string) throws ParseException {
234 Class<?> vc = getValueClass();
235 JFormattedTextField ftf = getFormattedTextField();
236
237 if (vc == null && ftf != null) {
238 Object value = ftf.getValue();
239
240 if (value != null) {
241 vc = value.getClass();
242 }
243 }
244 if (vc != null) {
245 Constructor cons;
246
247 try {
248 cons = vc.getConstructor(new Class[] { String.class });
249
250 } catch (NoSuchMethodException nsme) {
251 cons = null;
252 }
253
254 if (cons != null) {
255 try {
256 return cons.newInstance(new Object[] { string });
257 } catch (Throwable ex) {
258 throw new ParseException("Error creating instance", 0);
259 }
260 }
261 }
262 return string;
263 }
264
265 /**
266 * Converts the passed in Object into a String by way of the
267 * <code>toString</code> method.
268 *
269 * @throws ParseException if there is an error in the conversion
270 * @param value Value to convert
271 * @return String representation of value
272 */
273 public String valueToString(Object value) throws ParseException {
274 if (value == null) {
275 return "";
276 }
277 return value.toString();
278 }
279
280 /**
281 * Returns the <code>DocumentFilter</code> used to restrict the characters
282 * that can be input into the <code>JFormattedTextField</code>.
283 *
284 * @return DocumentFilter to restrict edits
285 */
286 protected DocumentFilter getDocumentFilter() {
287 if (documentFilter == null) {
288 documentFilter = new DefaultDocumentFilter();
289 }
290 return documentFilter;
291 }
292
293 /**
294 * Returns the <code>NavigationFilter</code> used to restrict where the
295 * cursor can be placed.
296 *
297 * @return NavigationFilter to restrict navigation
298 */
299 protected NavigationFilter getNavigationFilter() {
300 if (navigationFilter == null) {
301 navigationFilter = new DefaultNavigationFilter();
302 }
303 return navigationFilter;
304 }
305
306 /**
307 * Creates a copy of the DefaultFormatter.
308 *
309 * @return copy of the DefaultFormatter
310 */
311 public Object clone() throws CloneNotSupportedException {
312 DefaultFormatter formatter = (DefaultFormatter)super.clone();
313
314 formatter.navigationFilter = null;
315 formatter.documentFilter = null;
316 formatter.replaceHolder = null;
317 return formatter;
318 }
319
320
321 /**
322 * Positions the cursor at the initial location.
323 */
324 void positionCursorAtInitialLocation() {
325 JFormattedTextField ftf = getFormattedTextField();
326 if (ftf != null) {
327 ftf.setCaretPosition(getInitialVisualPosition());
328 }
329 }
330
331 /**
332 * Returns the initial location to position the cursor at. This forwards
333 * the call to <code>getNextNavigatableChar</code>.
334 */
335 int getInitialVisualPosition() {
336 return getNextNavigatableChar(0, 1);
337 }
338
339 /**
340 * Subclasses should override this if they want cursor navigation
341 * to skip certain characters. A return value of false indicates
342 * the character at <code>offset</code> should be skipped when
343 * navigating throught the field.
344 */
345 boolean isNavigatable(int offset) {
346 return true;
347 }
348
349 /**
350 * Returns true if the text in <code>text</code> can be inserted. This
351 * does not mean the text will ultimately be inserted, it is used if
352 * text can trivially reject certain characters.
353 */
354 boolean isLegalInsertText(String text) {
355 return true;
356 }
357
358 /**
359 * Returns the next editable character starting at offset incrementing
360 * the offset by <code>direction</code>.
361 */
362 private int getNextNavigatableChar(int offset, int direction) {
363 int max = getFormattedTextField().getDocument().getLength();
364
365 while (offset >= 0 && offset < max) {
366 if (isNavigatable(offset)) {
367 return offset;
368 }
369 offset += direction;
370 }
371 return offset;
372 }
373
374 /**
375 * A convenience methods to return the result of deleting
376 * <code>deleteLength</code> characters at <code>offset</code>
377 * and inserting <code>replaceString</code> at <code>offset</code>
378 * in the current text field.
379 */
380 String getReplaceString(int offset, int deleteLength,
381 String replaceString) {
382 String string = getFormattedTextField().getText();
383 String result;
384
385 result = string.substring(0, offset);
386 if (replaceString != null) {
387 result += replaceString;
388 }
389 if (offset + deleteLength < string.length()) {
390 result += string.substring(offset + deleteLength);
391 }
392 return result;
393 }
394
395 /*
396 * Returns true if the operation described by <code>rh</code> will
397 * result in a legal edit. This may set the <code>value</code>
398 * field of <code>rh</code>.
399 */
400 boolean isValidEdit(ReplaceHolder rh) {
401 if (!getAllowsInvalid()) {
402 String newString = getReplaceString(rh.offset, rh.length, rh.text);
403
404 try {
405 rh.value = stringToValue(newString);
406
407 return true;
408 } catch (ParseException pe) {
409 return false;
410 }
411 }
412 return true;
413 }
414
415 /**
416 * Invokes <code>commitEdit</code> on the JFormattedTextField.
417 */
418 void commitEdit() throws ParseException {
419 JFormattedTextField ftf = getFormattedTextField();
420
421 if (ftf != null) {
422 ftf.commitEdit();
423 }
424 }
425
426 /**
427 * Pushes the value to the JFormattedTextField if the current value
428 * is valid and invokes <code>setEditValid</code> based on the
429 * validity of the value.
430 */
431 void updateValue() {
432 updateValue(null);
433 }
434
435 /**
436 * Pushes the <code>value</code> to the editor if we are to
437 * commit on edits. If <code>value</code> is null, the current value
438 * will be obtained from the text component.
439 */
440 void updateValue(Object value) {
441 try {
442 if (value == null) {
443 String string = getFormattedTextField().getText();
444
445 value = stringToValue(string);
446 }
447
448 if (getCommitsOnValidEdit()) {
449 commitEdit();
450 }
451 setEditValid(true);
452 } catch (ParseException pe) {
453 setEditValid(false);
454 }
455 }
456
457 /**
458 * Returns the next cursor position from offset by incrementing
459 * <code>direction</code>. This uses
460 * <code>getNextNavigatableChar</code>
461 * as well as constraining the location to the max position.
462 */
463 int getNextCursorPosition(int offset, int direction) {
464 int newOffset = getNextNavigatableChar(offset, direction);
465 int max = getFormattedTextField().getDocument().getLength();
466
467 if (!getAllowsInvalid()) {
468 if (direction == -1 && offset == newOffset) {
469 // Case where hit backspace and only characters before
470 // offset are fixed.
471 newOffset = getNextNavigatableChar(newOffset, 1);
472 if (newOffset >= max) {
473 newOffset = offset;
474 }
475 }
476 else if (direction == 1 && newOffset >= max) {
477 // Don't go beyond last editable character.
478 newOffset = getNextNavigatableChar(max - 1, -1);
479 if (newOffset < max) {
480 newOffset++;
481 }
482 }
483 }
484 return newOffset;
485 }
486
487 /**
488 * Resets the cursor by using getNextCursorPosition.
489 */
490 void repositionCursor(int offset, int direction) {
491 getFormattedTextField().getCaret().setDot(getNextCursorPosition
492 (offset, direction));
493 }
494
495
496 /**
497 * Finds the next navigatable character.
498 */
499 int getNextVisualPositionFrom(JTextComponent text, int pos,
500 Position.Bias bias, int direction,
501 Position.Bias[] biasRet)
502 throws BadLocationException {
503 int value = text.getUI().getNextVisualPositionFrom(text, pos, bias,
504 direction, biasRet);
505
506 if (value == -1) {
507 return -1;
508 }
509 if (!getAllowsInvalid() && (direction == SwingConstants.EAST ||
510 direction == SwingConstants.WEST)) {
511 int last = -1;
512
513 while (!isNavigatable(value) && value != last) {
514 last = value;
515 value = text.getUI().getNextVisualPositionFrom(
516 text, value, bias, direction,biasRet);
517 }
518 int max = getFormattedTextField().getDocument().getLength();
519 if (last == value || value == max) {
520 if (value == 0) {
521 biasRet[0] = Position.Bias.Forward;
522 value = getInitialVisualPosition();
523 }
524 if (value >= max && max > 0) {
525 // Pending: should not assume forward!
526 biasRet[0] = Position.Bias.Forward;
527 value = getNextNavigatableChar(max - 1, -1) + 1;
528 }
529 }
530 }
531 return value;
532 }
533
534 /**
535 * Returns true if the edit described by <code>rh</code> will result
536 * in a legal value.
537 */
538 boolean canReplace(ReplaceHolder rh) {
539 return isValidEdit(rh);
540 }
541
542 /**
543 * DocumentFilter method, funnels into <code>replace</code>.
544 */
545 void replace(DocumentFilter.FilterBypass fb, int offset,
546 int length, String text,
547 AttributeSet attrs) throws BadLocationException {
548 ReplaceHolder rh = getReplaceHolder(fb, offset, length, text, attrs);
549
550 replace(rh);
551 }
552
553 /**
554 * If the edit described by <code>rh</code> is legal, this will
555 * return true, commit the edit (if necessary) and update the cursor
556 * position. This forwards to <code>canReplace</code> and
557 * <code>isLegalInsertText</code> as necessary to determine if
558 * the edit is in fact legal.
559 * <p>
560 * All of the DocumentFilter methods funnel into here, you should
561 * generally only have to override this.
562 */
563 boolean replace(ReplaceHolder rh) throws BadLocationException {
564 boolean valid = true;
565 int direction = 1;
566
567 if (rh.length > 0 && (rh.text == null || rh.text.length() == 0) &&
568 (getFormattedTextField().getSelectionStart() != rh.offset ||
569 rh.length > 1)) {
570 direction = -1;
571 }
572
573 if (getOverwriteMode() && rh.text != null &&
574 getFormattedTextField().getSelectedText() == null)
575 {
576 rh.length = Math.min(Math.max(rh.length, rh.text.length()),
577 rh.fb.getDocument().getLength() - rh.offset);
578 }
579 if ((rh.text != null && !isLegalInsertText(rh.text)) ||
580 !canReplace(rh) ||
581 (rh.length == 0 && (rh.text == null || rh.text.length() == 0))) {
582 valid = false;
583 }
584 if (valid) {
585 int cursor = rh.cursorPosition;
586
587 rh.fb.replace(rh.offset, rh.length, rh.text, rh.attrs);
588 if (cursor == -1) {
589 cursor = rh.offset;
590 if (direction == 1 && rh.text != null) {
591 cursor = rh.offset + rh.text.length();
592 }
593 }
594 updateValue(rh.value);
595 repositionCursor(cursor, direction);
596 return true;
597 }
598 else {
599 invalidEdit();
600 }
601 return false;
602 }
603
604 /**
605 * NavigationFilter method, subclasses that wish finer control should
606 * override this.
607 */
608 void setDot(NavigationFilter.FilterBypass fb, int dot, Position.Bias bias){
609 fb.setDot(dot, bias);
610 }
611
612 /**
613 * NavigationFilter method, subclasses that wish finer control should
614 * override this.
615 */
616 void moveDot(NavigationFilter.FilterBypass fb, int dot,
617 Position.Bias bias) {
618 fb.moveDot(dot, bias);
619 }
620
621
622 /**
623 * Returns the ReplaceHolder to track the replace of the specified
624 * text.
625 */
626 ReplaceHolder getReplaceHolder(DocumentFilter.FilterBypass fb, int offset,
627 int length, String text,
628 AttributeSet attrs) {
629 if (replaceHolder == null) {
630 replaceHolder = new ReplaceHolder();
631 }
632 replaceHolder.reset(fb, offset, length, text, attrs);
633 return replaceHolder;
634 }
635
636
637 /**
638 * ReplaceHolder is used to track where insert/remove/replace is
639 * going to happen.
640 */
641 static class ReplaceHolder {
642 /** The FilterBypass that was passed to the DocumentFilter method. */
643 DocumentFilter.FilterBypass fb;
644 /** Offset where the remove/insert is going to occur. */
645 int offset;
646 /** Length of text to remove. */
647 int length;
648 /** The text to insert, may be null. */
649 String text;
650 /** AttributeSet to attach to text, may be null. */
651 AttributeSet attrs;
652 /** The resulting value, this may never be set. */
653 Object value;
654 /** Position the cursor should be adjusted from. If this is -1
655 * the cursor position will be adjusted based on the direction of
656 * the replace (-1: offset, 1: offset + text.length()), otherwise
657 * the cursor position is adusted from this position.
658 */
659 int cursorPosition;
660
661 void reset(DocumentFilter.FilterBypass fb, int offset, int length,
662 String text, AttributeSet attrs) {
663 this.fb = fb;
664 this.offset = offset;
665 this.length = length;
666 this.text = text;
667 this.attrs = attrs;
668 this.value = null;
669 cursorPosition = -1;
670 }
671 }
672
673
674 /**
675 * NavigationFilter implementation that calls back to methods with
676 * same name in DefaultFormatter.
677 */
678 private class DefaultNavigationFilter extends NavigationFilter
679 implements Serializable {
680 public void setDot(FilterBypass fb, int dot, Position.Bias bias) {
681 JTextComponent tc = DefaultFormatter.this.getFormattedTextField();
682 if (tc.composedTextExists()) {
683 // bypass the filter
684 fb.setDot(dot, bias);
685 } else {
686 DefaultFormatter.this.setDot(fb, dot, bias);
687 }
688 }
689
690 public void moveDot(FilterBypass fb, int dot, Position.Bias bias) {
691 JTextComponent tc = DefaultFormatter.this.getFormattedTextField();
692 if (tc.composedTextExists()) {
693 // bypass the filter
694 fb.moveDot(dot, bias);
695 } else {
696 DefaultFormatter.this.moveDot(fb, dot, bias);
697 }
698 }
699
700 public int getNextVisualPositionFrom(JTextComponent text, int pos,
701 Position.Bias bias,
702 int direction,
703 Position.Bias[] biasRet)
704 throws BadLocationException {
705 if (text.composedTextExists()) {
706 // forward the call to the UI directly
707 return text.getUI().getNextVisualPositionFrom(
708 text, pos, bias, direction, biasRet);
709 } else {
710 return DefaultFormatter.this.getNextVisualPositionFrom(
711 text, pos, bias, direction, biasRet);
712 }
713 }
714 }
715
716
717 /**
718 * DocumentFilter implementation that calls back to the replace
719 * method of DefaultFormatter.
720 */
721 private class DefaultDocumentFilter extends DocumentFilter implements
722 Serializable {
723 public void remove(FilterBypass fb, int offset, int length) throws
724 BadLocationException {
725 JTextComponent tc = DefaultFormatter.this.getFormattedTextField();
726 if (tc.composedTextExists()) {
727 // bypass the filter
728 fb.remove(offset, length);
729 } else {
730 DefaultFormatter.this.replace(fb, offset, length, null, null);
731 }
732 }
733
734 public void insertString(FilterBypass fb, int offset,
735 String string, AttributeSet attr) throws
736 BadLocationException {
737 JTextComponent tc = DefaultFormatter.this.getFormattedTextField();
738 if (tc.composedTextExists() ||
739 Utilities.isComposedTextAttributeDefined(attr)) {
740 // bypass the filter
741 fb.insertString(offset, string, attr);
742 } else {
743 DefaultFormatter.this.replace(fb, offset, 0, string, attr);
744 }
745 }
746
747 public void replace(FilterBypass fb, int offset, int length,
748 String text, AttributeSet attr) throws
749 BadLocationException {
750 JTextComponent tc = DefaultFormatter.this.getFormattedTextField();
751 if (tc.composedTextExists() ||
752 Utilities.isComposedTextAttributeDefined(attr)) {
753 // bypass the filter
754 fb.replace(offset, length, text, attr);
755 } else {
756 DefaultFormatter.this.replace(fb, offset, length, text, attr);
757 }
758 }
759 }
760}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t