001 /* 002 * Copyright (C) 2012 eXo Platform SAS. 003 * 004 * This is free software; you can redistribute it and/or modify it 005 * under the terms of the GNU Lesser General Public License as 006 * published by the Free Software Foundation; either version 2.1 of 007 * the License, or (at your option) any later version. 008 * 009 * This software is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * You should have received a copy of the GNU Lesser General Public 015 * License along with this software; if not, write to the Free 016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 018 */ 019 package org.crsh.console; 020 021 import jline.console.Operation; 022 import org.crsh.keyboard.KeyHandler; 023 import org.crsh.keyboard.KeyType; 024 import org.crsh.shell.Shell; 025 import org.crsh.shell.ShellProcess; 026 import org.crsh.util.Utils; 027 028 import java.io.IOException; 029 import java.util.concurrent.BlockingDeque; 030 import java.util.concurrent.LinkedBlockingDeque; 031 import java.util.concurrent.atomic.AtomicReference; 032 import java.util.logging.Level; 033 import java.util.logging.Logger; 034 035 /** 036 * A console state machine, which delegates the state machine to the {@link Plugin} implementation. 037 * 038 * @author Julien Viet 039 */ 040 public class Console { 041 042 /** The logger. */ 043 private static final Logger log = Logger.getLogger(Console.class.getName()); 044 045 /** . */ 046 static final int RUNNING = 0; 047 048 /** . */ 049 static final int CLOSING = 1; 050 051 /** . */ 052 static final int CLOSED = 2; 053 054 /** . */ 055 final Shell shell; 056 057 /** The current handler. */ 058 final AtomicReference<Plugin> handler; 059 060 /** The buffer. */ 061 final BlockingDeque<KeyStroke> buffer; 062 063 /** . */ 064 final ConsoleDriver driver; 065 066 /** . */ 067 final Editor editor; 068 069 /** . */ 070 int status; 071 072 public Console(Shell shell, ConsoleDriver driver) throws NullPointerException { 073 if (shell == null) { 074 throw new NullPointerException("No null shell accepted"); 075 } 076 this.driver = driver; 077 this.shell = shell; 078 this.buffer = new LinkedBlockingDeque<KeyStroke>(1024); 079 this.handler = new AtomicReference<Plugin>(); 080 this.editor = new Editor(this); 081 this.status = RUNNING; 082 } 083 084 public void setMode(Mode mode) { 085 editor.setMode(mode); 086 } 087 088 public void toEmacs() { 089 setMode(Mode.EMACS); 090 } 091 092 public void toMove() { 093 setMode(Mode.VI_MOVE); 094 } 095 096 public void toInsert() { 097 setMode(Mode.VI_INSERT); 098 } 099 100 public Mode getMode() { 101 return editor.getMode(); 102 } 103 104 public void addModeListener(Runnable runnable) { 105 editor.addModeListener(runnable); 106 } 107 108 public boolean isRunning() { 109 return status == RUNNING; 110 } 111 112 /** 113 * Initiali 114 */ 115 public void init() { 116 // Take care of pormpt 117 String welcome = shell.getWelcome(); 118 if (welcome != null && welcome.length() > 0) { 119 try { 120 driver.write(welcome); 121 driver.flush(); 122 } 123 catch (IOException e) { 124 // Log it 125 } 126 } 127 edit(); 128 } 129 130 public Iterable<KeyStroke> getKeyBuffer() { 131 return buffer; 132 } 133 134 public void on(Operation operation, int... buffer) { 135 on(new KeyStroke(operation, buffer)); 136 } 137 138 public void on(KeyStroke keyStroke) { 139 140 // 141 if (keyStroke.operation == Operation.INTERRUPT) { 142 Plugin current = handler.get(); 143 if (current == null) { 144 throw new IllegalStateException("Not initialized"); 145 } else if (current instanceof ProcessHandler) { 146 ProcessHandler processHandler = (ProcessHandler)current; 147 ProcessHandler.Reader reader = processHandler.editor.get(); 148 if (reader != null) { 149 reader.thread.interrupt(); 150 } 151 processHandler.process.cancel(); 152 return; 153 } 154 } 155 buffer.add(keyStroke); 156 157 // 158 iterate(); 159 160 // This was modified by this thread during the loop 161 if (status == CLOSING) { 162 status = CLOSED; 163 Utils.close(driver); 164 } 165 } 166 167 public void on(KeyStroke[] keyStrokes) { 168 for (KeyStroke keyStroke : keyStrokes) { 169 on(keyStroke); 170 } 171 } 172 173 174 void close() { 175 if (status == RUNNING) { 176 status = CLOSED; 177 Utils.close(driver); 178 } 179 } 180 181 /** 182 * Switch to edit. 183 */ 184 Editor edit() { 185 String prompt = shell.getPrompt(); 186 if (prompt != null && prompt.length() > 0) { 187 try { 188 driver.write(prompt); 189 driver.flush(); 190 } 191 catch (IOException e) { 192 // Swallow for now... 193 } 194 } 195 editor.reset(); 196 handler.set(editor); 197 return editor; 198 } 199 200 /** 201 * Process the state machine. 202 */ 203 void iterate() { 204 while (status == RUNNING) { 205 Plugin current = handler.get(); 206 KeyStroke key = buffer.poll(); 207 if (key != null) { 208 if (current == null) { 209 throw new IllegalStateException("Not initialized"); 210 } else if (current instanceof Editor) { 211 Editor editor = (Editor)current; 212 EditorAction action = editor.getMode().on(key); 213 if (action != null) { 214 String line = editor.append(action, key.sequence); 215 if (line != null) { 216 ShellProcess process = shell.createProcess(line); 217 ProcessHandler context = new ProcessHandler(this, process); 218 handler.set(context); 219 process.execute(context); 220 } 221 } 222 } else if (current instanceof ProcessHandler) { 223 ProcessHandler processHandler = (ProcessHandler)current; 224 ProcessHandler.Reader reader = processHandler.editor.get(); 225 if (reader != null) { 226 EditorAction action = editor.getMode().on(key); 227 if (action != null) { 228 String s = reader.editor.append(action, key.sequence); 229 if (s != null) { 230 reader.line.add(s); 231 } 232 } 233 } else { 234 KeyHandler keyHandler = processHandler.process.getKeyHandler(); 235 if (keyHandler != null) { 236 KeyType type = key.map(); 237 try { 238 keyHandler.handle(type, key.sequence); 239 } 240 catch (Throwable t) { 241 // Perhaps handle better this and treat error / exception differently 242 log.log(Level.SEVERE, "Key handler " + keyHandler + " failure", t); 243 } 244 } else { 245 buffer.addFirst(key); 246 } 247 return; 248 } 249 } else { 250 throw new UnsupportedOperationException(); 251 } 252 } else { 253 return; 254 } 255 } 256 } 257 }