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.shell.Shell;
023 import org.crsh.shell.ShellProcess;
024 import org.crsh.util.Utils;
025
026 import java.io.IOException;
027 import java.util.concurrent.BlockingDeque;
028 import java.util.concurrent.LinkedBlockingDeque;
029 import java.util.concurrent.atomic.AtomicReference;
030
031 /**
032 * A console state machine, which delegates the state machine to the {@link Plugin} implementation.
033 *
034 * @author Julien Viet
035 */
036 public class Console {
037
038 /** . */
039 final Shell shell;
040
041 /** The current handler. */
042 final AtomicReference<Plugin> handler;
043
044 /** The buffer. */
045 final BlockingDeque<KeyStroke> buffer;
046
047 /** . */
048 final ConsoleDriver driver;
049
050 /** . */
051 final Editor editor;
052
053 /** . */
054 boolean running;
055
056 public Console(Shell shell, ConsoleDriver driver) throws NullPointerException {
057 if (shell == null) {
058 throw new NullPointerException("No null shell accepted");
059 }
060 this.driver = driver;
061 this.shell = shell;
062 this.buffer = new LinkedBlockingDeque<KeyStroke>(1024);
063 this.handler = new AtomicReference<Plugin>();
064 this.editor = new Editor(this);
065 this.running = true;
066 }
067
068 public void setMode(Mode mode) {
069 editor.setMode(mode);
070 }
071
072 public void toEmacs() {
073 setMode(Mode.EMACS);
074 }
075
076 public void toMove() {
077 setMode(Mode.VI_MOVE);
078 }
079
080 public void toInsert() {
081 setMode(Mode.VI_INSERT);
082 }
083
084 public Mode getMode() {
085 return editor.getMode();
086 }
087
088 public void addModeListener(Runnable runnable) {
089 editor.addModeListener(runnable);
090 }
091
092 public boolean isRunning() {
093 return running;
094 }
095
096 /**
097 * Initiali
098 */
099 public void init() {
100 // Take care of pormpt
101 String welcome = shell.getWelcome();
102 if (welcome != null && welcome.length() > 0) {
103 try {
104 driver.write(welcome);
105 driver.flush();
106 }
107 catch (IOException e) {
108 // Log it
109 }
110 }
111 edit();
112 }
113
114 public Iterable<KeyStroke> getKeyBuffer() {
115 return buffer;
116 }
117
118 public void on(Operation operation, int... buffer) {
119 on(new KeyStroke(operation, buffer));
120 }
121
122 public void on(KeyStroke keyStroke) {
123 if (keyStroke.operation == Operation.INTERRUPT) {
124 Plugin current = handler.get();
125 if (current == null) {
126 throw new IllegalStateException("Not initialized");
127 } else if (current instanceof ProcessHandler) {
128 ProcessHandler processHandler = (ProcessHandler)current;
129 ProcessHandler.Reader reader = processHandler.editor.get();
130 if (reader != null) {
131 reader.thread.interrupt();
132 }
133 processHandler.process.cancel();
134 return;
135 }
136 }
137 buffer.add(keyStroke);
138 iterate();
139 }
140
141 public void on(KeyStroke[] keyStrokes) {
142 for (KeyStroke keyStroke : keyStrokes) {
143 on(keyStroke);
144 }
145 }
146
147
148 void close() {
149 running = false;
150 Utils.close(driver);
151 }
152
153 /**
154 * Switch to edit.
155 */
156 Editor edit() {
157 String prompt = shell.getPrompt();
158 if (prompt != null && prompt.length() > 0) {
159 try {
160 driver.write(prompt);
161 driver.flush();
162 }
163 catch (IOException e) {
164 // Swallow for now...
165 }
166 }
167 editor.reset();
168 handler.set(editor);
169 return editor;
170 }
171
172 /**
173 * Process the state machine.
174 */
175 void iterate() {
176 while (running) {
177 Plugin current = handler.get();
178 KeyStroke key = buffer.poll();
179 if (key != null) {
180 if (current == null) {
181 throw new IllegalStateException("Not initialized");
182 } else if (current instanceof Editor) {
183 Editor editor = (Editor)current;
184 EditorAction action = editor.getMode().on(key);
185 if (action != null) {
186 String line = editor.append(action, key.sequence);
187 if (line != null) {
188 ShellProcess process = shell.createProcess(line);
189 ProcessHandler context = new ProcessHandler(this, process);
190 handler.set(context);
191 process.execute(context);
192 }
193 }
194 } else if (current instanceof ProcessHandler) {
195 ProcessHandler processHandler = (ProcessHandler)current;
196 ProcessHandler.Reader reader = processHandler.editor.get();
197 if (reader != null) {
198 EditorAction action = editor.getMode().on(key);
199 if (action != null) {
200 String s = reader.editor.append(action, key.sequence);
201 if (s != null) {
202 reader.line.add(s);
203 }
204 }
205 } else {
206 KeyHandler keyHandler = processHandler.process.getKeyHandler();
207 if (keyHandler != null) {
208 KeyType type = KeyType.map(key.operation, key.sequence);
209 keyHandler.handle(type, key.sequence);
210 } else {
211 buffer.addFirst(key);
212 }
213 return;
214 }
215 } else {
216 throw new UnsupportedOperationException();
217 }
218 } else {
219 return;
220 }
221 }
222 }
223 }