1 package org.jruby.demo; 2 3 import java.awt.Color ; 4 import java.awt.Point ; 5 import java.awt.event.KeyEvent ; 6 import java.awt.event.KeyListener ; 7 import java.io.IOException ; 8 import java.io.OutputStream ; 9 import java.util.Iterator ; 10 import java.util.LinkedList ; 11 import java.util.List ; 12 13 import javax.swing.DefaultListCellRenderer ; 14 import javax.swing.JComboBox ; 15 import javax.swing.SwingUtilities ; 16 import javax.swing.plaf.basic.BasicComboPopup ; 17 import javax.swing.text.AbstractDocument ; 18 import javax.swing.text.AttributeSet ; 19 import javax.swing.text.BadLocationException ; 20 import javax.swing.text.DocumentFilter ; 21 import javax.swing.text.JTextComponent ; 22 import javax.swing.text.MutableAttributeSet ; 23 import javax.swing.text.SimpleAttributeSet ; 24 import javax.swing.text.StyleConstants ; 25 26 import org.jruby.Ruby; 27 import org.jruby.RubyModule; 28 import org.jruby.ext.Readline; 29 import org.jruby.runtime.Arity; 30 import org.jruby.runtime.Block; 31 import org.jruby.runtime.builtin.IRubyObject; 32 import org.jruby.runtime.callback.Callback; 33 34 public class TextAreaReadline extends OutputStream implements KeyListener { 35 36 JTextComponent area; 37 private int startPos; 38 private String currentLine; 39 40 private Object amEditing = new Object (); 41 42 public MutableAttributeSet promptStyle; 43 public MutableAttributeSet inputStyle; 44 public MutableAttributeSet outputStyle; 45 public MutableAttributeSet resultStyle; 46 47 private JComboBox completeCombo; 48 private BasicComboPopup completePopup; 49 private int start; 50 private int end; 51 52 public TextAreaReadline(JTextComponent area) { 53 this(area, null); 54 } 55 56 public TextAreaReadline(JTextComponent area, final String message) { 57 this.area = area; 58 59 area.addKeyListener(this); 60 61 if (area.getDocument() instanceof AbstractDocument ) 63 ((AbstractDocument ) area.getDocument()).setDocumentFilter( 64 new DocumentFilter () { 65 public void insertString(DocumentFilter.FilterBypass fb, int offset, String string, AttributeSet attr) throws BadLocationException { 66 if (offset >= startPos) super.insertString(fb, offset, string, attr); 67 } 68 69 public void remove(DocumentFilter.FilterBypass fb, int offset, int length) throws BadLocationException { 70 if (offset >= startPos) super.remove(fb, offset, length); 71 } 72 73 public void replace(DocumentFilter.FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { 74 if (offset >= startPos) super.replace(fb, offset, length, text, attrs); 75 } 76 } 77 ); 78 79 promptStyle = new SimpleAttributeSet (); 80 StyleConstants.setForeground(promptStyle, new Color (0xa4, 0x00, 0x00)); 81 82 inputStyle = new SimpleAttributeSet (); 83 StyleConstants.setForeground(inputStyle, new Color (0x20, 0x4a, 0x87)); 84 85 outputStyle = new SimpleAttributeSet (); 86 StyleConstants.setForeground(outputStyle, Color.darkGray); 87 88 resultStyle = new SimpleAttributeSet (); 89 StyleConstants.setItalic(resultStyle, true); 90 StyleConstants.setForeground(resultStyle, new Color (0x20, 0x4a, 0x87)); 91 92 completeCombo = new JComboBox (); 93 completeCombo.setRenderer(new DefaultListCellRenderer ()); completePopup = new BasicComboPopup (completeCombo); 95 96 if (message != null) { 97 final MutableAttributeSet messageStyle = new SimpleAttributeSet (); 98 StyleConstants.setBackground(messageStyle, area.getForeground()); 99 StyleConstants.setForeground(messageStyle, area.getBackground()); 100 SwingUtilities.invokeLater( new Runnable () { 101 public void run() { 102 append(message, messageStyle); 103 } 104 }); 105 } 106 } 107 108 public void hookIntoRuntime(final Ruby runtime) { 109 110 runtime.getLoadService().require("readline"); 111 RubyModule readlineM = runtime.getModule("Readline"); 112 113 readlineM.defineModuleFunction("readline", new Callback() { 114 public IRubyObject execute(IRubyObject recv, IRubyObject[] args, Block block) { 115 return runtime.newString(readLine(args[0].toString())); 116 } 117 public Arity getArity() { return Arity.twoArguments(); } 118 }); 119 } 120 121 protected void completeAction(KeyEvent event) { 122 if (Readline.getCompletor() == null) return; 123 124 event.consume(); 125 126 if (completePopup.isVisible()) return; 127 128 List candidates = new LinkedList (); 129 String bufstr = null; 130 try { 131 bufstr = area.getText(startPos, area.getCaretPosition() - startPos); 132 } catch (BadLocationException e) { 133 return; 134 } 135 136 int cursor = area.getCaretPosition() - startPos; 137 138 int position = Readline.getCompletor().complete(bufstr, cursor, candidates); 139 140 if (candidates.isEmpty()) 142 return; 143 144 if (candidates.size() == 1) { 145 replaceText(startPos + position, area.getCaretPosition(), (String ) candidates.get(0)); 146 return; 147 } 148 149 start = startPos + position; 150 end = area.getCaretPosition(); 151 152 Point pos = area.getCaret().getMagicCaretPosition(); 153 154 int cutoff = bufstr.substring(position).lastIndexOf('.') + 1; 156 start += cutoff; 157 158 if (candidates.size() < 10) 159 completePopup.getList().setVisibleRowCount(candidates.size()); 160 else 161 completePopup.getList().setVisibleRowCount(10); 162 163 completeCombo.removeAllItems(); 164 for (Iterator i = candidates.iterator(); i.hasNext();) { 165 String item = (String ) i.next(); 166 if (cutoff != 0) item = item.substring(cutoff); 167 completeCombo.addItem(item); 168 } 169 170 completePopup.show(area, pos.x, pos.y + area.getFontMetrics(area.getFont()).getHeight()); 171 } 172 173 protected void backAction(KeyEvent event) { 174 if (area.getCaretPosition() <= startPos) 175 event.consume(); 176 } 177 178 protected void upAction(KeyEvent event) { 179 event.consume(); 180 181 if (completePopup.isVisible()) { 182 int selected = completeCombo.getSelectedIndex() - 1; 183 if (selected < 0) return; 184 completeCombo.setSelectedIndex(selected); 185 return; 186 } 187 188 if (!Readline.getHistory().next()) currentLine = getLine(); 190 else 191 Readline.getHistory().previous(); 193 if (!Readline.getHistory().previous()) return; 194 195 String oldLine = Readline.getHistory().current().trim(); 196 replaceText(startPos, area.getDocument().getLength(), oldLine); 197 } 198 199 protected void downAction(KeyEvent event) { 200 event.consume(); 201 202 if (completePopup.isVisible()) { 203 int selected = completeCombo.getSelectedIndex() + 1; 204 if (selected == completeCombo.getItemCount()) return; 205 completeCombo.setSelectedIndex(selected); 206 return; 207 } 208 209 if (!Readline.getHistory().next()) return; 210 211 String oldLine; 212 if (!Readline.getHistory().next()) oldLine = currentLine; 214 else { 215 Readline.getHistory().previous(); oldLine = Readline.getHistory().current().trim(); 217 } 218 219 replaceText(startPos, area.getDocument().getLength(), oldLine); 220 } 221 222 protected void replaceText(int start, int end, String replacement) { 223 try { 224 area.getDocument().remove(start, end - start); 225 area.getDocument().insertString(start, replacement, inputStyle); 226 } catch (BadLocationException e) { 227 e.printStackTrace(); 228 } 229 } 230 231 protected String getLine() { 232 try { 233 return area.getText(startPos, area.getDocument().getLength() - startPos); 234 } catch (BadLocationException e) { 235 e.printStackTrace(); 236 } 237 return null; 238 } 239 240 protected void enterAction(KeyEvent event) { 241 event.consume(); 242 243 if (completePopup.isVisible()) { 244 if (completeCombo.getSelectedItem() != null) 245 replaceText(start, end, (String ) completeCombo.getSelectedItem()); 246 completePopup.setVisible(false); 247 return; 248 } 249 250 append("\n", null); 251 synchronized (amEditing) { 252 amEditing.notify(); 253 } 254 } 255 256 public String readLine(final String prompt) 257 { 258 SwingUtilities.invokeLater( new Runnable () { 259 public void run() { 260 append(prompt.trim(), promptStyle); 261 append(" ", inputStyle); area.setCaretPosition(area.getDocument().getLength()); 263 startPos = area.getDocument().getLength(); 264 } 265 }); 266 267 Readline.getHistory().moveToEnd(); 268 269 synchronized (amEditing) { 270 try { 271 amEditing.wait(); 272 } catch (InterruptedException e) { } 273 } 274 String result = getLine().trim(); 275 return result; 276 } 277 278 public void keyPressed(KeyEvent event) { 279 int code = event.getKeyCode(); 280 switch (code) { 281 case KeyEvent.VK_TAB: completeAction(event); break; 282 case KeyEvent.VK_LEFT: 283 case KeyEvent.VK_BACK_SPACE: 284 backAction(event); break; 285 case KeyEvent.VK_UP: upAction(event); break; 286 case KeyEvent.VK_DOWN: downAction(event); break; 287 case KeyEvent.VK_ENTER: enterAction(event); break; 288 case KeyEvent.VK_HOME: event.consume(); area.setCaretPosition(startPos); break; 289 } 290 291 if (completePopup.isVisible() && 292 code != KeyEvent.VK_TAB && 293 code != KeyEvent.VK_UP && 294 code != KeyEvent.VK_DOWN ) 295 completePopup.setVisible(false); 296 } 297 298 public void keyReleased(KeyEvent arg0) { } 299 300 public void keyTyped(KeyEvent arg0) { } 301 302 303 304 protected void append(String toAppend, AttributeSet style) { 305 try { 306 area.getDocument().insertString(area.getDocument().getLength(), toAppend, style); 307 } catch (BadLocationException e) { } 308 } 309 310 public void writeLine(final String line) { 311 SwingUtilities.invokeLater( new Runnable () { 312 public void run() { 313 if (line.startsWith("=>")) 314 append(line, resultStyle); 315 else 316 append(line, outputStyle); 317 } 318 }); 319 } 320 321 public void write(int b) throws IOException { 322 writeLine("" + b); 323 } 324 325 public void write(byte[] b, int off, int len) { 326 writeLine(new String (b, off, len)); 327 } 328 329 public void write(byte[] b) { 330 writeLine(new String (b)); 331 } 332 } 333 | Popular Tags |