Простой редактор текста by java through design
Всем привет. Для того чтобы писать код нужен удобный инструмент, кто-то пользуется IDE, кто-то редакторами текста Emacs, VIM. Зачастую, чтобы программисту было удобно, разработчику надо соблюсти много нюансов, тоесть в одном случае есть ускорение разработки, но решение тяжеловесное, в другом случае, разработчик, чтобы пользоваться редактором начинает писать код, чтобы писать код. Конечно это крайности, и проще поставить IDE/VSCode/etc… , но что если можно найти эту золотую середину?!
Неоднократно автор пытался делать текстовый редактор, но что-то было всё не то, пока не вспомнил, что есть старый добрый SWING, и java by design иногда круто и интересно.
В этой статье хочу показать как получилось минимальными усилиями сделать то, что на С или С++ потребует больших знаний без QT/GTK/etc…
Начнем!
Пользуюсь системой FreeBSD 14.2 Release
% uname -rv
14.2-RELEASE FreeBSD 14.2-RELEASE releng/14.2-n269506-c8918d6c7412 GENERIC
Как поставить java (на всякий случай)
% pkg install openjdk17-17.0.13+11.1 openjdk17-jre-17.0.13+11.1
коммеентарий-строка#в /etc/fstab надо дополнить следующее и перезагрузиться или примонтировать чего не достаточно
fdesc /dev/fd fdescfs rw 0 0
proc /proc procfs rw 0 0
На всякий случай как скомпилировать программу
% cat > main.java
class Main{
public static void main(String[] args){
System.out.println("Hello World");
}
}
^C
% javac main.java
% java main.java
Hello World
%
% cat > MANIFEST.MF
Manifest-Version: 1.0
Main-Class: Main
^C
% jar -cvfm app.jar MANIFEST.MF *.class;
added manifest
adding: Main.class(in = 413) (out= 285)(deflated 30%)
% java -jar ./app.jar
Hello World
%
Создадим скрипт на сборку
% cat > compile.sh
javac Main.java; jar -cvfm app.jar MANIFEST.MF *.class; java -jar ./app.jar
^C
% cat compile.sh
javac Main.java; jar -cvfm app.jar MANIFEST.MF *.class; java -jar ./app.jar
% chmod +x compile.sh
Приступим к подходу шаблона над текстовым редактором
В ресурсах указано чем я пользовался java-swing-create-a-simple-text-editor.
давайте рассмотрим псевдокод того к чему надо приблизиться,
{
Scroll nScrolls;
MenubarWithItemsOrCli barTop,barBottom;
Frame frame;
TextArea text,numbers,cli;//stringsWithMetadata
Process p;
Prompt prompt;
Command command;
}=> superClass
{
load barTop(MenuAndItems);
load Group(Scroll(numbers,text));
load barBottom(Scroll(cli));
connectAllLoadingToFrame(barTop,GroupWithOneScroll,barBottom);
setCursorInValidPostoTextArea();
setSettingsForFrame;
Show();
}=> мы имеем строку куда вводятся символы и число строк
{}//Events
{}//ProcessIfNeedProvide
{
call Editor;
}
давайте проинициализируем суперКласс
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
import java.io.*;
import java.awt.event.*;
import javax.swing.plaf.metal.*;
import javax.swing.text.*;
import java.io.IOException;
class Editor extends JFrame implements ActionListener {
// Text component
private JTextArea t; //code
private JTextArea t1; //lineNumbers
private JTextArea t2; //mini cli
// Frame
private JFrame f; //frame-likewWindow
private StringBuilder LineN; //string
private String prompter=" \n % "; //prompt
private String commander; //command after prompt
private int LineNN=1; //number of lines on doc
// Constructor
public Editor()
{
// Create a frame
JFrame.setDefaultLookAndFeelDecorated(true);
f = new JFrame("editor");
try {
// Set metal look and feel
UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
// Set theme to ocean
MetalLookAndFeel.setCurrentTheme(new OceanTheme());//set theme read in doc
}
catch (Exception e) {
}
//config jtextarea for numberLines
t1 = new JTextArea("1\n",2,1);//start jtextarea with a1,a2,a3 - text , row, collumns
t1.setEditable(false);//disable edit
//config jtextarea for textSpace
t = new JTextArea("111",21,20);
t.setTabSize(1);//1 tab
t.setCaretPosition(t.getDocument().getLength());//set cursor position in doc
t.addKeyListener(new CounterLines());//connect listen keyboard
// Create a menubar
JMenuBar mb = new JMenuBar();//menubar top
// Create amenu for menu
JMenu m1 = new JMenu("File");//menu for file
// Create menu items
JMenuItem mi1 = new JMenuItem("New");//item for file
JMenuItem mi2 = new JMenuItem("Open");//item for file
JMenuItem mi3 = new JMenuItem("Save");//item for file
JMenuItem mi9 = new JMenuItem("Print");//item for file
// Add action listener
mi1.addActionListener(this);//connect event
mi2.addActionListener(this);
mi3.addActionListener(this);
mi9.addActionListener(this);
m1.add(mi1);//connect to File
m1.add(mi2);
m1.add(mi3);
m1.add(mi9);
// Create amenu for menu
JMenu m2 = new JMenu("Edit");//menu for Edit
// Create menu items
JMenuItem mi4 = new JMenuItem("cut");//item for edit
JMenuItem mi5 = new JMenuItem("copy");//item for edit
JMenuItem mi6 = new JMenuItem("paste");//item for edit
// Add action listener
mi4.addActionListener(this);//connect event
mi5.addActionListener(this);
mi6.addActionListener(this);
m2.add(mi4);//connect to Edit
m2.add(mi5);
m2.add(mi6);
JMenu mc = new JMenu("Close");//menu button for close instant
mc.addMouseListener(new ExitAction());//connect event
mb.add(m1);//connect all menu to menubar-top
mb.add(m2);
mb.add(mc);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//exit on button close X
JMenuBar mbConsole = new JMenuBar();//create menubar-bottom for cli
t2 = new JTextArea(prompter,10,20);//create jtextarea with a1 a2 a3
t2.addKeyListener(new CmrPrompter());//connect keyevent
mbConsole.add(t2);//connect jtextarea-cli to bottom menubar
//collors for all jtextareas t=code t1=numbers t2=cli
Color c = new Color(0,0,0,255);
Color cf = new Color(200,200,200,255);
t.setBackground(c);
t.setForeground(cf);
Color c1 = new Color(50,50,50,255);
Color cf1 = new Color(200,200,200,255);
t1.setBackground(c1);
t1.setForeground(cf1);
Color c2 = new Color(60,60,60,255);
Color cf2 = new Color(200,200,200,255);
t2.setBackground(c2);
t2.setForeground(cf2);
JPanel panel = new JPanel();//create panel
panel.setLayout(new BorderLayout());//create template for complians
panel.add(t1, BorderLayout.WEST);//left because numbers
panel.add(t, BorderLayout.CENTER);//right extend to center
f.add(mb,BorderLayout.NORTH);//menubar to up
f.add(new JScrollPane(mbConsole),BorderLayout.SOUTH);//set scroll to cli and set bottom position
f.add(new JScrollPane(panel));//connect all panel to frame
f.setSize(1200, 900);//set size wxh
centreWindow(f);//set center position
f.show();//show()
t.requestFocus();//fix some situation with current pos and doc
t.setCaretPosition(t.getText().length());//fix some situation with cursor
}
//используем performs
// If a button is pressed
public void actionPerformed(ActionEvent e)//actions for items
{
String s = e.getActionCommand();
if (s.equals("cut")) {
t.cut();
}
else if (s.equals("copy")) {
t.copy();
}
else if (s.equals("paste")) {
t.paste();
}
else if (s.equals("Save")) {
// Create an object of JFileChooser class
JFileChooser j = new JFileChooser("f:");
// Invoke the showsSaveDialog function to show the save dialog
int r = j.showSaveDialog(null);
if (r == JFileChooser.APPROVE_OPTION) {
// Set the label to the path of the selected directory
File fi = new File(j.getSelectedFile().getAbsolutePath());
try {
// Create a file writer
FileWriter wr = new FileWriter(fi, false);
// Create buffered writer to write
BufferedWriter w = new BufferedWriter(wr);
// Write
w.write(t.getText());
w.flush();
w.close();
}
catch (Exception evt) {
JOptionPane.showMessageDialog(f, evt.getMessage());
}
}
// If the user cancelled the operation
else
JOptionPane.showMessageDialog(f, "the user cancelled the operation");
}
else if (s.equals("Print")) {
try {
// print the file
t.print();
}
catch (Exception evt) {
JOptionPane.showMessageDialog(f, evt.getMessage());
}
}
else if (s.equals("Open")) {
// Create an object of JFileChooser class
JFileChooser j = new JFileChooser("f:");
// Invoke the showsOpenDialog function to show the save dialog
int r = j.showOpenDialog(null);
// If the user selects a file
if (r == JFileChooser.APPROVE_OPTION) {
// Set the label to the path of the selected directory
File fi = new File(j.getSelectedFile().getAbsolutePath());
try {
// String
String s1 = "", sl = "";
// File reader
FileReader fr = new FileReader(fi);
// Buffered reader
BufferedReader br = new BufferedReader(fr);
// Initialize sl
sl = br.readLine();
// Take the input from the file
while ((s1 = br.readLine()) != null) {
sl = sl + "\n" + s1;
LineNN+=1;//set linenumbers while if line
t1.append(""+LineNN);
t1.append(" \n");
}
// Set the text
t.setText(sl);
}
catch (Exception evt) {
JOptionPane.showMessageDialog(f, evt.getMessage());
}
}
// If the user cancelled the operation
else
JOptionPane.showMessageDialog(f, "the user cancelled the operation");
}
else if (s.equals("New")) {
t.setText("");
}
else if (s.equals("Close")) {
}
repaint();//
revalidate();
}
//выход по Close
//exit after clicked on Close Menu!=item
class ExitAction extends MouseInputAdapter {
public void mouseClicked(MouseEvent mouseEvent) {
System.exit(0);
}
}
//подсчет строк и применение в буффере счетчика строк
class CounterLines extends KeyAdapter {//listen keyboard event for enter if enter add \n +i to numberLines
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER){
LineNN+=1;
t1.append(""+LineNN);
t1.append(" \n");
}
}
}
//установим окно в центр
public static void centreWindow(Window frame) {//set center Window
Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();//get size
int x = (int) ((dimension.getWidth() - frame.getWidth()) / 2);//
int y = (int) ((dimension.getHeight() - frame.getHeight()) / 2);
frame.setLocation(x, y);
}
//поток где проводится команда
class CmrPrompter extends KeyAdapter{//cmd\n - \n emulate enter
public void keyPressed(KeyEvent e){
if(e.getKeyCode() == KeyEvent.VK_ENTER){
Runtime runtime = Runtime.getRuntime();//default process
//get token from doc https://godbolt.org/z/9e5W3zf81
String s1 = t2.getText();
String parts[] = s1.split("\n");
int sz = parts.length;
String part1 = parts[sz-1];
String parts2[] = part1.split("% ");
int sz1 = parts2.length;
commander=parts2[sz1-1];//set command token
try {//try
Process pR = runtime.exec(commander);//call program
BufferedReader cmdLineOut = new BufferedReader(new InputStreamReader(pR.getInputStream()));
PrintWriter cmdLineIn = new PrintWriter(pR.getOutputStream());
String s = null;
while((s=cmdLineOut.readLine())!=null){//read line by line
t2.append("\n");
t2.append(s);
//System.out.println(cmdLineIn.toString());//commentary dont touching
}
}
catch (IOException e1){
e1.printStackTrace();
}
commander= "";//free command string
t2.append("\n");//set to the end//combo div-s
t2.append(" % ");//set to the end//combo div-s
t2.setText(t2.getText().substring(0,t2.getText().lastIndexOf("\r\n")));//set cursor after prompt
System.gc();//just
}
}
}
// Main class
public static void main(String[] args)
{
Editor e = new Editor();//jump to editor
}
}
Этот код решает на стадии инициализации группирование обьектов, а именно — выставление двух елементов — буффер числа строк и текстовое поле пользователя в 1 скролл, выставление верхнего бара, применение в нижнем баре текстовое поле для использования в нём проведение пользовательских комманд.
В стадии использования как редактор он пока простенький, на первых подходах свою задачу выполняет.
если скомпилировать этот код и запустить
% ./compile.sh
Note: Editor.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
added manifest
adding: Editor.class(in = 6437) (out= 3606)(deflated 43%)
adding: Editor$1.class(in = 767) (out= 521)(deflated 32%)
adding: Editor$CmrPrompter.class(in = 1966) (out= 1124)(deflated 42%)
adding: Editor$CounterLines.class(in = 1129) (out= 658)(deflated 41%)
adding: Editor$ExitAction.class(in = 489) (out= 347)(deflated 29%)
adding: Editor$SMenuListener.class(in = 626) (out= 386)(deflated 38%)
adding: Editor$TextAreaOutputStream.class(in = 951) (out= 566)(deflated 40%)
adding: Editor$TextAreaOutputStream$1.class(in = 781) (out= 461)(deflated 40%)
adding: SMenuListener.class(in = 606) (out= 356)(deflated 41%)

Важный момент автор заметил при проведении процесса выпадение без остановки
Exception in thread "AWT-EventQueue-0" java.lang.StringIndexOutOfBoundsException: begin 0, end -1, length 73
это происходит в строке, где я решаю очистить строку от последнего елемента чтобы после проведения комманды курсор был как в терминале на своём месте, я пока пропустил это предупреждение.
t2.setText(t2.getText().substring(0,t2.getText().lastIndexOf("\r\n"))); //set cursor after prompt
Теперь мы знаем как сделать простой редактор. А вам приходилось делать текстовый редактор? Поделитесь об этом в комментарии
Ресурсы:
JTextArea — разбор java-swing-create-a-simple-text-editor
BorderLayout
Runtime — java-runtime-getruntime-method
swing-jtextfield
abstract, Class, subclasses