1

Creating a program that involves needing the users favourite music artist. I want them to be able to edit their choice if they so please.

When they want to edit their favourite artist, they are shown what their current favourite artist is through the terminal: Your favourite artist is: example

I want the user to be able to delete the text "example" and replace with something else, and then for it to be stored in a variable.

Visualisation of what I want the user to be able to do:

Your favourite artist is: example

User is now deleting the text "example"

Your favourite artist is: exa

User has replaced "example" with "newArtist"

Your favourite artist is: newArtist

I have looked around and have been unable to find anything on this particular question. I have seen programs utilise this feature, however they weren't written in Java.

6
  • 1
    You cannot treat System.in and System.out as the same thing in the command line. Commented Oct 12, 2023 at 15:35
  • 2
    This question boils down to 'how do I write java' or possibly 'how do I read and write files'. I don't think you'll have much luck with such a broad question on SO, that's not really what it is for. There is no special magic answer here - it's just 'create a text file and write a template to it, tell the user to edit it, then read it'. Look at the java.nio.file package for the API to interact with files. Commented Oct 12, 2023 at 15:38
  • 6
    @rzwitserloot I may be wrong here, but the question looks more like "how do I print a line and let the user edit that very same line, in place", which is itself a totally different can of worms, Commented Oct 12, 2023 at 15:44
  • 1
    Java itself can't do this, but have a look at Java: how can I modify console output? or Edit a given String over the Console in Java for inspiration on what to further search on. Commented Oct 12, 2023 at 15:59
  • 1
    @FedericoklezCulloca With your hint I have written up an answer, and retracted the close vote. Gracias! Commented Oct 12, 2023 at 23:57

2 Answers 2

1

Your question implies you hold a certain idea about how command line programs work that is common, obvious, but incorrect.

You appear to believe that a program simply always reads input from the keyboard, and writes output to the screen. Or, at least, that this is what System.in and System.out mean.

Not so. This means two things:

  1. What you want is doable but very complicated and not portable, i.e. you need different code for different platforms, and
  2. Even explaining it is kinda complicated.

This answer might just sail right over your head. There's still some use to be had: Just accept that, for reasons, what you want is not possible without involving extensive third party libraries (dependencies: Separate jars you stick on the classpath when you launch your app), such as Lanterna which then allow you to do this.

Explanation of why this is hard

System.in means 'the process input stream' and System.out means 'the process output stream'. Not 'the keyboard' and 'the screen'.

So what are 'the process input' and 'the process output'? Whatever thing asks the OS to launch your application specifies what they are as you launch it.

And who launches it? Not 'you' - no, you provide some input to an application (cmd.exe on windows usually, perhaps /bin/bash on most other systems such as linux or macos), and that is just an application, nothing special about it. You type:

java MyProgram.java

And as you hit each character, cmd.exe or whatever it might be accepts that, renders a little picture that looks like e.g. a j to the screen, and when you hit enter, 'parses' what you typed and this results in cmd.exe making a call to the OS to launch a new process, and in doing so, cmd.exe tells your system what to hook up to that process' system in and system out.

By default, that'll be the keyboard in cooked mode (no actual bytes show up for System.in.read() to read unless you hit enter), and the terminal itself which will just take any bytes sent to it via System.out and print them on the screen as characters.

And that is why System.in is the keyboard and System.out is the screen. But, I can tell the terminal app to do something else:

java MyProgram.java <somefile.txt >/dev/printer

Here, the input is not 'what the user types on the keyboard' but 'the contents of the file somefile.txt. As in, if in your java app you do: System.in.read() you get the first byte from the somefile.txt file. And System.out.println("Hello") would make your printer start printing.

So what? How does that affect my question?

Well, think about it: If System.in can be anything, and System.out can be anything, how can you possibly link the two? Or, thinking differently, the notion of 'the output is a thing that you can edit' isn't part of what process standard output is about. A printer can not 'edit' - once you print Your favourite artist is: example, then that's printed. You can't then delete example, printers do not as a rule ship with a baked in cartridge containing whiteout or whatever.

Yes, sure, you have no intention of ever redirecting standard out to a printer. That's not the point. The point is: At a fundamental level, sysout is just a thing that can receive bytes, and that is all it is. It has no 'move the cursor to this position', 'go into overwrite mode'. There is no cursor - the abstraction is just 'send bytes here', and that's it. That gives your system the flexibility to let you hook it up to a printer and whatnot - to be able to be hooked up to a process' standard out, you just need to be capable of receiving bytes. The flipside is, you cant say: Move the cursor back.

Except, if you can. Let's trace the exact flow of what happens in this sequence of events:

> cat MyApp.java
public class MyApp {
  public static void main(String[] args) {
     System.out.print("Hi");
  }
}

> java MyApp.java

Given that you didn't use < or > to modify anything, and we typed this in a terminal, System.out is the terminal window itself. Specifically, print("Hi") will send the byte value for the H (72) to 'the standard output' and then 105 (the byte value for i). The standard output is hooked up to the terminal window which knows 'if I receive 72, that means I should move the cursor one position to the right and show a little picture that looks like an H.'. And that is why you see Hi appear.

Some terminals will interpret certain byte value sequences as 'commands'. Such as 'move the cursor to the left', 'the foreground color is now light blue', 'emit a bell sound through the system speaker', and so on.

Okay so how do I do what I want?

... with the command stuff I wrote in the last paragraph of the previous section: There is sort of, kind of, a way. First print Enter your favorite artist: example as you usually would, but then send a command to move the cursor back so that it is over the 'e' in example. But your standard System.in defaults to 'cooked' mod which means that you get no input whatsoever until enter is pressed so this is at the very least going to look very ugly. The cursor is where we want it to be but that's not enough. We optimally want to receive a keypress as it arrives (instead of getting a whole line at once), and act accordingly. For example, after pressing the first key, we can send a bunch of commands that space over / wipe out the text example and then goes back to where the e was and print the character the user typed.

Hence, we need to do two things:

  1. Check if System.in is currently typed system input in 'cooked mode', and if so, take it out of cooked mode. Otherwise, crash or swap to a code path that knows that there is no reversing or editing anything.

  2. Emit to System.out these special commands. What are they?

The problem is, this is the part that is system dependent. On windows (cmd.exe) the ways to do this are completely different from /bin/bash.

Hence, to develop this properly you'd have to delve into terminal commands for each platform you intend to support. For example, on most posixen this should work to uncook sysin:

String[] cmd = {"/bin/sh", "-c", "stty raw </dev/tty"};
Runtime.getRuntime().exec(cmd).waitFor();
System.out.println("Now type and watch as I deal with each typed thing as you type it - marvelous!");
System.out.println("Q to exit.");
while (true) {
  int t = System.in.read();
  System.out.println("\nYou typed value: " + t + " - which is '" + (char) t + "'!");
  if (t == 'Q') System.exit(0);
}

This actually works on my mac, but taking the terminal out of cooked mode also means that \n just advanced to the next line and no longer puts the cursor to the start. Also, the 'raw mode' thing is permanent - even after the app exits my terminal is all wonky and I'd have to close and reopen. I can fix the newline issue by using print instead of println and adding \r\n to the end of each string. I can fix the 'it remains in raw mode' by going back to cooked mode. Here's a much nicer take:

String[] cmd = {"/bin/sh", "-c", "stty raw </dev/tty"};
Runtime.getRuntime().exec(cmd).waitFor();
System.out.print("Now type and watch as I deal with each typed thing as you type it - marvelous!\r\n");
System.out.print("Q to exit.\r\n");
while (true) {
  int t = System.in.read();
  System.out.print("\r\nYou typed value: " + t + " - which is '" + (char)
t + "'!\r\n");
  if (t == 'Q') break;
}

cmd = new String[] {"/bin/sh", "-c", "stty cooked </dev/tty"};
Runtime.getRuntime().exec(cmd).waitFor();

But here's the problem: That will definitely not work on windows. That might not work on various linuxen. It's also going to completely fail if you start this app with java MyApp.java <stuff >something_else. And we just opened the door to allowing us to act immediately as the user starts typing (no waiting for enter), we haven't tackled moving the cursor around the screen yet.

Which will be a very similar story: I can show you how to do it... for some specific OS and terminal. Not generally.

Deps to the rescue!

You can use ncurses or Lanterna or some other library that gives you text UIs for java support to do this, these libraries have done the footwork of figuring out how to do that on each OS as best as possible. You should definitely use those libraries.

Or, ditch the plan and just use System.in/System.out as normal (don't involve having the user edit what they type), or, ditch System.in/System.out and use java.swing or similar to make a little UI. Or make it a web app. Which is what most folks do these days.

Sign up to request clarification or add additional context in comments.

Comments

0

In Java, state is traditionally held in a variables. You could also store data in an external file or a database.

The standard Java library handles input and output as separate actions.

Here is a simple REPL program written in Java:

import java.util.Scanner;

public class Input {
    static private String userFavoriteArtist = "";

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);

        // Enter "q" to exit the loop...
        while (true) {
            System.out.print("Enter your favorite artist: ");
            userFavoriteArtist = scan.nextLine();
            if (userFavoriteArtist.equals("q")) {
                break;
            }
            System.out.println("Your favorite artist is: " + userFavoriteArtist);
        }

        scan.close();
    }
}

1 Comment

Right, but the question is how to print a line of a text that can then be edited by the user. Not just looked at because it got printed and the cursor is nowhere to be found (which java itself can't do)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.