/**
* 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.jledit;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.LinkedList;
/**
* An {@link org.jledit.Editor} implementation for {@link String} objects.
*/
public class StringEditor implements Editor<String> {
private String source;
private Charset charset = Charset.defaultCharset();
private int line = 1;
private int column = 1;
private Boolean dirty = false;
private final LinkedList<String> lines = new LinkedList<String>();
private ContentManager contentManager = new FileContentManager();
public StringEditor() {
lines.add("");
}
public StringEditor(String content) {
String[] contentLines = content.split("\n|\r");
lines.addAll(Arrays.asList(contentLines));
}
@Override
public synchronized int getLine() {
return line;
}
@Override
public synchronized int getColumn() {
return column;
}
/**
* Moves the index to the specified line and column.
* If the requested line is greater that the actual lines number and exception is thrown.
* If the requested column is greater than the line width, the index is moved to the end of the line.
*
* @param line
* @param column
* @throws IndexOutOfBoundsException When the requested line exceeds the number of lines in the content.
*/
@Override
public synchronized void move(int line, int column) {
if (lines() < line) {
this.column = 1;
} else {
String targetLine = lines.get(line - 1);
if (targetLine.length() < column) {
this.column = targetLine.length() + 1;
} else {
this.column = column;
}
}
this.line = line;
}
/**
* Moves the cursors to the start of the line.
*/
@Override
public synchronized void moveToStartOfLine() {
move(line, 1);
}
/**
* Moves cursor to the end of the line.
*/
@Override
public synchronized void moveToEndOfLine() {
move(line, getContent(line).length());
}
/**
* Moves the cursors to the start of the line.
*/
@Override
public void moveToStartOfFile() {
move(1, 1);
}
/**
* Moves cursor to the end of the line.
*/
@Override
public void moveToEndOfFile() {
int targetLine = lines.size() + 1;
move(targetLine, getContent(targetLine).length());
}
@Override
public synchronized void put(String str) {
while (lines() < line) {
lines.add("");
}
String currentLine = lines.remove(line - 1);
String beforePut = currentLine.substring(0, column - 1);
String afterPut = column - 1 < currentLine.length() ? currentLine.substring(column - 1) : "";
String[] modifiedContent = (beforePut + str + afterPut).split("\n|\r");
if (modifiedContent.length == 1) {
lines.add(line - 1, modifiedContent[0]);
column += str.length();
} else {
String lastLine = null;
for (String l : modifiedContent) {
lines.add(line++ - 1, l);
lastLine = l;
}
column = lastLine.length() - afterPut.length();
}
}
@Override
public synchronized String delete() {
if (lines() < line) {
this.column = 1;
return "\n";
}
String currentLine = lines.remove(line - 1);
if (column - 1 == currentLine.length()) {
String nextLine = "";
if (lines() >= line) {
nextLine = lines.remove(line - 1);
}
lines.add(line - 1, currentLine + nextLine);
return "\n";
} else if (column - 1 < currentLine.length()) {
String deleted = currentLine.substring(column - 1, column);
lines.add(line - 1, currentLine.substring(0, column - 1) + currentLine.substring(column));
return deleted;
} else {
return "\n";
}
}
@Override
public synchronized String backspace() {
if (line == 1 && column == 1) {
return "";
} else if (lines() < line) {
return "";
} else if (column == 1) {
String currentLine = lines.remove(line - 1);
String previousLine = lines.remove(line - 2);
lines.add(line - 2, previousLine + currentLine);
line--;
column = previousLine.length();
return "\n";
} else {
String currentLine = lines.remove(line - 1);
String deleted = currentLine.substring(column - 2, column - 1);
lines.add(line - 1, currentLine.substring(0, column - 2) + currentLine.substring(column - 1));
column--;
return deleted;
}
}
@Override
public synchronized void newLine() {
while (lines() < line) {
lines.add("");
}
if (column == 1) {
lines.add(line - 1, "");
} else {
String currentLine = lines.remove(line - 1);
//The character under the cursor should just move to the next line.
String beforeNewLine = currentLine.substring(0, column - 1);
String afterNewLine = currentLine.substring(column - 1);
lines.add(line - 1, afterNewLine);
lines.add(line - 1, beforeNewLine);
}
line++;
column = 1;
}
@Override
public synchronized void mergeLine() {
if (line < lines.size()) {
String currentLine = lines.remove(line - 1);
String nextLine = lines.remove(line - 1);
lines.add(line - 1, currentLine + nextLine);
}
}
/**
* Finds the next appearance of the String.
*
* @param str
*/
@Override
public synchronized void findNext(String str) {
boolean found = false;
int startLine = line;
int startColumn = column + 1; //We always start one char after the cursor position.
while (!found && startLine <= lines.size()) {
String currentLine = getContent(startLine);
String linePart = currentLine.length() > startColumn ? currentLine.substring(startColumn - 1) : "";
if (linePart.contains(str)) {
column = startColumn + linePart.indexOf(str);
line = startLine;
found = true;
} else {
startLine++;
startColumn = 1;
}
}
}
/**
* Returns the number of lines.
*
* @return
*/
@Override
public synchronized int lines() {
return lines.size();
}
/**
* Finds the next appearance of the String.
*
* @param str
*/
@Override
public synchronized void findPrevious(String str) {
boolean found = false;
int startLine = line;
int startColumn = column;
while (!found && startLine > 0) {
String currentLine = getContent(startLine);
String linePart = currentLine.substring(0, startColumn - 1);
if (linePart.contains(str)) {
column = linePart.indexOf(str, 0) + 1;
line = startLine;
found = true;
} else {
startLine--;
currentLine = getContent(startLine);
startColumn = currentLine.length() + 1;
}
}
}
@Override
public synchronized void open(String source) throws IOException {
this.source = source;
this.charset = contentManager.detectCharset(source);
lines.clear();
try {
String[] contentLines = contentManager.load(source).split("\n|\r");
lines.addAll(Arrays.asList(contentLines));
} catch (Exception ex) {
lines.add("");
//noop
}
this.line = 1;
this.column = 1;
}
@Override
public synchronized void save(String target) throws IOException {
if (target != null) {
this.source = target;
}
if (source == null) {
throw new IOException("No target specified for saving.");
} else if (!contentManager.save(getContent(), charset, source)) {
throw new IOException("Failed to save to target.");
}
}
@Override
public synchronized void close() throws IOException {
this.source = null;
this.charset = null;
lines.clear();
}
@Override
public synchronized String getContent() {
StringBuilder contentBuilder = new StringBuilder();
for (String l : lines) {
contentBuilder.append(l).append("\n");
}
return contentBuilder.toString();
}
@Override
public String getContent(int line) {
if (line <= lines.size()) {
return lines.get(line - 1);
} else {
return "";
}
}
/**
* Returns the {@link java.io.File} being edited.
*
* @return
*/
@Override
public String getSource() {
return source;
}
public Boolean isDirty() {
return dirty;
}
public void setDirty(Boolean dirty) {
this.dirty = dirty;
}
public ContentManager getContentManager() {
return contentManager;
}
public void setContentManager(ContentManager contentManager) {
this.contentManager = contentManager;
}
}