##// END OF EJS Templates
* ConsoleWidget now has better support for non-GUI tab completion. Multiple matches are formatted into columns....
epatters -
Show More
@@ -0,0 +1,160 b''
1 """Return compact set of columns as a string with newlines for an
2 array of strings.
3
4 Adapted from the routine of the same name inside cmd.py
5
6 Author: Rocky Bernstein.
7 License: MIT Open Source License.
8 """
9
10 import types
11
12 def columnize(array, displaywidth=80, colsep = ' ',
13 arrange_vertical=True, ljust=True, lineprefix=''):
14 """Return a list of strings as a compact set of columns arranged
15 horizontally or vertically.
16
17 For example, for a line width of 4 characters (arranged vertically):
18 ['1', '2,', '3', '4'] => '1 3\n2 4\n'
19
20 or arranged horizontally:
21 ['1', '2,', '3', '4'] => '1 2\n3 4\n'
22
23 Each column is only as wide as necessary. By default, columns are
24 separated by two spaces - one was not legible enough. Set "colsep"
25 to adjust the string separate columns. Set `displaywidth' to set
26 the line width.
27
28 Normally, consecutive items go down from the top to bottom from
29 the left-most column to the right-most. If "arrange_vertical" is
30 set false, consecutive items will go across, left to right, top to
31 bottom."""
32 if not isinstance(array, list) and not isinstance(array, tuple):
33 raise TypeError, (
34 'array needs to be an instance of a list or a tuple')
35
36 array = [str(i) for i in array]
37
38 # Some degenerate cases
39 size = len(array)
40 if 0 == size:
41 return "<empty>\n"
42 elif size == 1:
43 return '%s\n' % str(array[0])
44
45 displaywidth = max(4, displaywidth - len(lineprefix))
46 if arrange_vertical:
47 array_index = lambda nrows, row, col: nrows*col + row
48 # Try every row count from 1 upwards
49 for nrows in range(1, size):
50 ncols = (size+nrows-1) // nrows
51 colwidths = []
52 totwidth = -len(colsep)
53 for col in range(ncols):
54 # get max column width for this column
55 colwidth = 0
56 for row in range(nrows):
57 i = array_index(nrows, row, col)
58 if i >= size: break
59 x = array[i]
60 colwidth = max(colwidth, len(x))
61 pass
62 colwidths.append(colwidth)
63 totwidth += colwidth + len(colsep)
64 if totwidth > displaywidth:
65 break
66 pass
67 if totwidth <= displaywidth:
68 break
69 pass
70 # The smallest number of rows computed and the
71 # max widths for each column has been obtained.
72 # Now we just have to format each of the
73 # rows.
74 s = ''
75 for row in range(nrows):
76 texts = []
77 for col in range(ncols):
78 i = row + nrows*col
79 if i >= size:
80 x = ""
81 else:
82 x = array[i]
83 texts.append(x)
84 while texts and not texts[-1]:
85 del texts[-1]
86 for col in range(len(texts)):
87 if ljust:
88 texts[col] = texts[col].ljust(colwidths[col])
89 else:
90 texts[col] = texts[col].rjust(colwidths[col])
91 pass
92 pass
93 s += "%s%s\n" % (lineprefix, str(colsep.join(texts)))
94 pass
95 return s
96 else:
97 array_index = lambda nrows, row, col: ncols*(row-1) + col
98 # Try every column count from size downwards
99 prev_colwidths = []
100 colwidths = []
101 for ncols in range(size, 0, -1):
102 # Try every row count from 1 upwards
103 min_rows = (size+ncols-1) // ncols
104 for nrows in range(min_rows, size):
105 rounded_size = nrows * ncols
106 colwidths = []
107 totwidth = -len(colsep)
108 for col in range(ncols):
109 # get max column width for this column
110 colwidth = 0
111 for row in range(1, nrows+1):
112 i = array_index(nrows, row, col)
113 if i >= rounded_size: break
114 elif i < size:
115 x = array[i]
116 colwidth = max(colwidth, len(x))
117 pass
118 pass
119 colwidths.append(colwidth)
120 totwidth += colwidth + len(colsep)
121 if totwidth >= displaywidth:
122 break
123 pass
124 if totwidth <= displaywidth and i >= rounded_size-1:
125 # Found the right nrows and ncols
126 nrows = row
127 break
128 elif totwidth >= displaywidth:
129 # Need to reduce ncols
130 break
131 pass
132 if totwidth <= displaywidth and i >= rounded_size-1:
133 break
134 pass
135 # The smallest number of rows computed and the
136 # max widths for each column has been obtained.
137 # Now we just have to format each of the
138 # rows.
139 s = ''
140 for row in range(1, nrows+1):
141 texts = []
142 for col in range(ncols):
143 i = array_index(nrows, row, col)
144 if i >= size:
145 break
146 else: x = array[i]
147 texts.append(x)
148 pass
149 for col in range(len(texts)):
150 if ljust:
151 texts[col] = texts[col].ljust(colwidths[col])
152 else:
153 texts[col] = texts[col].rjust(colwidths[col])
154 pass
155 pass
156 s += "%s%s\n" % (lineprefix, str(colsep.join(texts)))
157 pass
158 return s
159 pass
160
@@ -5,6 +5,7 b' import sys'
5 from PyQt4 import QtCore, QtGui
5 from PyQt4 import QtCore, QtGui
6
6
7 # Local imports
7 # Local imports
8 from IPython.external.columnize import columnize
8 from ansi_code_processor import QtAnsiCodeProcessor
9 from ansi_code_processor import QtAnsiCodeProcessor
9 from completion_widget import CompletionWidget
10 from completion_widget import CompletionWidget
10
11
@@ -29,9 +30,6 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
29 # priority (when it has focus) over, e.g., window-level menu shortcuts.
30 # priority (when it has focus) over, e.g., window-level menu shortcuts.
30 override_shortcuts = False
31 override_shortcuts = False
31
32
32 # The number of spaces to show for a tab character.
33 tab_width = 8
34
35 # Protected class variables.
33 # Protected class variables.
36 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
34 _ctrl_down_remap = { QtCore.Qt.Key_B : QtCore.Qt.Key_Left,
37 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
35 QtCore.Qt.Key_F : QtCore.Qt.Key_Right,
@@ -62,6 +60,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
62 self._prompt_pos = 0
60 self._prompt_pos = 0
63 self._reading = False
61 self._reading = False
64 self._reading_callback = None
62 self._reading_callback = None
63 self._tab_width = 8
65
64
66 # Set a monospaced font.
65 # Set a monospaced font.
67 self.reset_font()
66 self.reset_font()
@@ -113,7 +112,7 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
113 self._context_menu.exec_(event.globalPos())
112 self._context_menu.exec_(event.globalPos())
114
113
115 def dragMoveEvent(self, event):
114 def dragMoveEvent(self, event):
116 """ Reimplemented to disable dropping text.
115 """ Reimplemented to disable moving text by drag and drop.
117 """
116 """
118 event.ignore()
117 event.ignore()
119
118
@@ -467,6 +466,21 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
467 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
466 font = QtGui.QFont(name, QtGui.qApp.font().pointSize())
468 font.setStyleHint(QtGui.QFont.TypeWriter)
467 font.setStyleHint(QtGui.QFont.TypeWriter)
469 self._set_font(font)
468 self._set_font(font)
469
470 def _get_tab_width(self):
471 """ The width (in terms of space characters) for tab characters.
472 """
473 return self._tab_width
474
475 def _set_tab_width(self, tab_width):
476 """ Sets the width (in terms of space characters) for tab characters.
477 """
478 font_metrics = QtGui.QFontMetrics(self.font)
479 self.setTabStopWidth(tab_width * font_metrics.width(' '))
480
481 self._tab_width = tab_width
482
483 tab_width = property(_get_tab_width, _set_tab_width)
470
484
471 #---------------------------------------------------------------------------
485 #---------------------------------------------------------------------------
472 # 'ConsoleWidget' abstract interface
486 # 'ConsoleWidget' abstract interface
@@ -563,9 +577,34 b' class ConsoleWidget(QtGui.QPlainTextEdit):'
563 if self.gui_completion:
577 if self.gui_completion:
564 self._completion_widget.show_items(cursor, items)
578 self._completion_widget.show_items(cursor, items)
565 else:
579 else:
566 text = '\n'.join(items) + '\n'
580 text = self.format_as_columns(items)
567 self._append_plain_text_keeping_prompt(text)
581 self._append_plain_text_keeping_prompt(text)
568
582
583 def format_as_columns(self, items, separator=' ', vertical=True):
584 """ Transform a list of strings into a single string with columns.
585
586 Parameters
587 ----------
588 items : sequence [str]
589 The strings to process.
590
591 separator : str, optional [default is two spaces]
592 The string that separates columns.
593
594 vertical: bool, optional [default True]
595 If set, consecutive items will be arranged from top to bottom, then
596 from left to right. Otherwise, consecutive items will be aranged
597 from left to right, then from top to bottom.
598
599 Returns
600 -------
601 The formatted string.
602 """
603 font_metrics = QtGui.QFontMetrics(self.font)
604 width = self.width() / font_metrics.width(' ')
605 return columnize(items, displaywidth=width,
606 colsep=separator, arrange_vertical=vertical)
607
569 def _get_block_plain_text(self, block):
608 def _get_block_plain_text(self, block):
570 """ Given a QTextBlock, return its unformatted text.
609 """ Given a QTextBlock, return its unformatted text.
571 """
610 """
@@ -63,10 +63,7 b' class FrontendHighlighter(PygmentsHighlighter):'
63 class FrontendWidget(HistoryConsoleWidget):
63 class FrontendWidget(HistoryConsoleWidget):
64 """ A Qt frontend for a generic Python kernel.
64 """ A Qt frontend for a generic Python kernel.
65 """
65 """
66
66
67 # ConsoleWidget interface.
68 tab_width = 4
69
70 # Emitted when an 'execute_reply' is received from the kernel.
67 # Emitted when an 'execute_reply' is received from the kernel.
71 executed = QtCore.pyqtSignal(object)
68 executed = QtCore.pyqtSignal(object)
72
69
@@ -86,6 +83,7 b' class FrontendWidget(HistoryConsoleWidget):'
86 self._kernel_manager = None
83 self._kernel_manager = None
87
84
88 # Configure the ConsoleWidget.
85 # Configure the ConsoleWidget.
86 self.tab_width = 4
89 self._set_continuation_prompt('... ')
87 self._set_continuation_prompt('... ')
90
88
91 self.document().contentsChange.connect(self._document_contents_change)
89 self.document().contentsChange.connect(self._document_contents_change)
@@ -121,7 +119,7 b' class FrontendWidget(HistoryConsoleWidget):'
121 prompt created. When triggered by an Enter/Return key press,
119 prompt created. When triggered by an Enter/Return key press,
122 'interactive' is True; otherwise, it is False.
120 'interactive' is True; otherwise, it is False.
123 """
121 """
124 complete = self._input_splitter.push(source.replace('\t', ' '))
122 complete = self._input_splitter.push(source.expandtabs(4))
125 if interactive:
123 if interactive:
126 complete = not self._input_splitter.push_accepts_more()
124 complete = not self._input_splitter.push_accepts_more()
127 return complete
125 return complete
General Comments 0
You need to be logged in to leave comments. Login now