/*
* Copyright 2010 NCHOVY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.krakenapps.script;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import org.krakenapps.ansicode.ClearScreenCode;
import org.krakenapps.ansicode.EraseLineCode;
import org.krakenapps.ansicode.MoveToCode;
import org.krakenapps.ansicode.ScrollCode;
import org.krakenapps.ansicode.SetColorCode;
import org.krakenapps.ansicode.ClearScreenCode.Option;
import org.krakenapps.ansicode.SetColorCode.Color;
import org.krakenapps.api.FunctionKeyEvent;
import org.krakenapps.api.FunctionKeyEventListener;
import org.krakenapps.api.ScriptContext;
import org.krakenapps.api.WindowSizeEventListener;
import org.krakenapps.api.FunctionKeyEvent.KeyCode;
public class Editor {
private ScriptContext context;
private boolean dirty;
private int lineno;
private int x = 0;
private int y = 0;
private List<String> lines;
public Editor(ScriptContext context) {
this.context = context;
}
private class EditorSizeChangedCallback implements WindowSizeEventListener {
@Override
public void sizeChanged(int width, int height) {
try {
render();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void open(final File f) throws IOException {
lines = readFile(f);
EditorSizeChangedCallback sizeCallback = new EditorSizeChangedCallback();
FunctionKeyEventListener callback = new FunctionKeyEventListener() {
@Override
public void keyPressed(FunctionKeyEvent e) {
try {
KeyCode c = e.getKeyCode();
// you should prevent additional rendering because it will
// conflict with screen clear routine at exit.
if (c == KeyCode.CTRL_C || c == KeyCode.CTRL_D)
return;
int maxHeight = getMaxHeight();
// page down
if (c == KeyCode.HOME || c == KeyCode.CTRL_A) {
x = 0;
renderStatus();
moveTo();
} else if (c == KeyCode.END || c == KeyCode.CTRL_E) {
x = lines.get(lineno).length();
renderStatus();
moveTo();
} else if (c == KeyCode.DOWN || c == KeyCode.CTRL_N) {
if (lineno == lines.size() - 1)
return;
lineno++;
if (y < maxHeight) {
y++;
renderStatus();
moveTo();
} else {
// scroll down
context.print(new SetColorCode(Color.Black, Color.White));
context.print(new ScrollCode(true));
context.print(new EraseLineCode(EraseLineCode.Option.EntireLine));
context.print(new MoveToCode(0));
context.print(lines.get(lineno));
// title and cursor
renderTitle();
renderStatus();
moveTo();
}
} else if (c == KeyCode.UP || c == KeyCode.CTRL_P) {
if (lineno == 0)
return;
lineno--;
if (y > 0) {
y--;
renderStatus();
moveTo();
} else {
context.print(new SetColorCode(Color.Black, Color.White));
context.print(new ScrollCode(false));
renderTitle();
renderStatus();
context.print(new MoveToCode(1, 2));
context.print(new EraseLineCode(EraseLineCode.Option.EntireLine));
context.print(lines.get(lineno));
// goto position after print
moveTo();
}
} else if (c == KeyCode.RIGHT) {
x++;
renderStatus();
moveTo();
} else if (c == KeyCode.LEFT) {
if (x > 0)
x--;
renderStatus();
moveTo();
} else if (c == KeyCode.BACKSPACE) {
if (x > 0) {
x--;
String line = lines.get(lineno);
String newLine = line.substring(0, x) + line.substring(x + 1);
updateCurrentLine(lineno, newLine);
}
} else if (c == KeyCode.CTRL_S) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(f);
Charset utf8 = Charset.forName("utf-8");
for (String line : lines) {
fos.write(line.getBytes(utf8));
fos.write("\n".getBytes());
}
dirty = false;
} finally {
if (fos != null)
fos.close();
renderStatus();
}
} else {
render();
}
} catch (Exception ex) {
}
}
};
try {
context.turnEchoOff();
context.addWindowSizeEventListener(sizeCallback);
context.getInputStream().addFunctionKeyEventListener(callback);
render();
// char input loop
while (true) {
char c = context.read();
if (c == '\r' || c == '\n') {
String line = getCurrentLine();
String left = line.substring(0, x);
String right = line.substring(x);
// scroll down and redraw title and status
context.print(new SetColorCode(Color.Black, Color.White));
context.print(new ScrollCode(false));
renderTitle();
renderStatus();
// cut right part off
updateCurrentLine(lineno, left);
if (y < getMaxHeight())
y++;
// set position and insert new line
x = 0;
lineno++;
lines.add(lineno, right);
// redraw from 0 to new line after scrolling
moveTo(0, 0);
for (int i = 0; i <= lineno; i++) {
moveTo(0, i);
context.print(new EraseLineCode(EraseLineCode.Option.EntireLine));
context.print(lines.get(lineno - (lineno - i)));
}
// locate cursor
moveTo();
} else {
String line = getCurrentLine();
String newLine = line.substring(0, x) + c + line.substring(x);
x++;
updateCurrentLine(lineno, newLine);
}
}
} catch (InterruptedException e) {
} finally {
context.removeWindowSizeEventListener(sizeCallback);
context.getInputStream().removeFunctionKeyEventListener(callback);
context.print(new SetColorCode(Color.Black, Color.White));
context.print(new MoveToCode(1, 1));
context.print(new ClearScreenCode(Option.EntireScreen));
context.turnEchoOn();
}
}
private int getMaxHeight() {
int maxHeight = context.getHeight() - 3;
return maxHeight;
}
private String getCurrentLine() {
if (lines.size() > lineno)
return lines.get(lineno);
else {
lines.add(lineno, "");
return "";
}
}
/**
* update line data, redraw, and locate cursor
*/
private void updateCurrentLine(int no, String newLine) {
dirty = true;
if (lines.size() > lineno)
lines.remove(lineno);
lines.add(lineno, newLine);
context.print(new EraseLineCode(EraseLineCode.Option.EntireLine));
context.print(new MoveToCode(0));
context.print(newLine);
renderStatus();
moveTo();
}
private void render() throws FileNotFoundException, IOException {
context.print(new MoveToCode(1, 1));
context.print(new SetColorCode(Color.Black, Color.White, true));
context.print(new ClearScreenCode(Option.EntireScreen));
renderTitle();
renderStatus();
context.print(new SetColorCode(Color.Black, Color.White, false));
context.print(new MoveToCode(1, 2));
int height = context.getHeight() - 2;
for (int i = 0; i < height; i++) {
if (i >= lines.size())
break;
String line = lines.get(i);
if (line.length() > context.getWidth())
line = line.substring(context.getWidth());
context.println(line);
}
moveTo();
}
private void renderTitle() {
// title
context.print(new MoveToCode(1, 1));
context.print(new SetColorCode(Color.Blue, Color.White, false));
context.print(new EraseLineCode(EraseLineCode.Option.EntireLine));
context.print("Kraken Editor");
context.print(new SetColorCode(Color.Black, Color.White, false));
}
private void renderStatus() {
String dirtyStatus = dirty ? "[*]" : "[ ]";
// status
context.print(new MoveToCode(1, context.getHeight()));
context.print(new SetColorCode(Color.Blue, Color.White, false));
context.print(new EraseLineCode(EraseLineCode.Option.EntireLine));
context.print(dirtyStatus + " Column: " + (x + 1) + ", Line: " + (lineno + 1));
context.print(new SetColorCode(Color.Black, Color.White, false));
moveTo();
}
private void moveTo(int x, int y) {
context.print(new MoveToCode(x + 1, y + 2));
}
private void moveTo() {
context.print(new MoveToCode(x + 1, y + 2));
}
private List<String> readFile(File f) {
List<String> lines = new LinkedList<String>();
FileInputStream is = null;
try {
is = new FileInputStream(f);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
while (true) {
String line = br.readLine();
if (line == null)
break;
lines.add(line);
}
} catch (IOException e) {
} finally {
if (is != null)
try {
is.close();
} catch (IOException e) {
}
}
return lines;
}
@SuppressWarnings("unused")
private void drawText(int x, int y, String s) {
context.print(new MoveToCode(x, y));
context.print(s);
}
@SuppressWarnings("unused")
private void drawBox(int x1, int y1, int x2, int y2) {
// first line
context.print(new MoveToCode(x1, y1));
context.print("+");
for (int i = x1 + 1; i < x2; i++) {
context.print("-");
}
context.print("+");
// body
for (int i = y1 + 1; i < y2; i++) {
context.print(new MoveToCode(x1, i));
context.print("|");
for (int j = x1 + 1; j < x2; j++)
context.print(" ");
context.print("|");
}
// last line
context.print(new MoveToCode(x1, y2));
context.print("+");
for (int i = x1 + 1; i < x2; i++) {
context.print("-");
}
context.print("+");
}
@SuppressWarnings("unused")
private void drawHorizontalLine(int x1, int x2, int y) {
context.print(new MoveToCode(x1, y));
context.print("+");
for (int i = x1 + 1; i < x2; i++)
context.print("-");
context.print("+");
}
@SuppressWarnings("unused")
private void drawVerticalLine(int x1, int y1, int y2) {
context.print(new MoveToCode(x1, y1));
for (int i = y1; i <= y2; i++) {
context.print(new MoveToCode(x1, i));
context.print("|");
}
}
}