dd-ex: the main program

Almost all the required functions have been defined before, so all we need to do is to read a command and call them. To implement the autoprint command, however, it is useful to define a function to print the current line number and contents:
  lineout () {
    Echo -n "$lineno: "
    $dd if=$ed.c 2>/dev/null
  }
The variable autoprint will contain the name of a function to automatically call after some commands. This can be set to lineout to turn autoprint on, to true to do nothing (or to any other function which doesn't do anything useful), or to whatever other function we might need when extending the editor.

The current file is in the variable name, while state can have values "closed", "open", or "changed": they mean that there is no current file, that there is one and any changes have been saved, and that there is one with unsaved changed, respectively.

The variables are initialised as follows:

  state=closed
  name=
  autoprint=true
The main program is a loop which reads a command, with a large case statement to execute it. We use "command:state" in the case, so we can easily distinuish conditions like closing an unopened file, and so on. The loop is:
  while true
  do
    Echo -n '> '
    read cmd arg
    case "$cmd:$state" in

.... cases here ....

      # empty commands are ignored
      :*) ;;

      # anything else produces an error
      *) Echo "Command not understood" ;;
    esac
  done
Each command requires one or more case label, depending on whether we want to distinguish between different states. They are shown below.

The loop would never terminate, but we can always use exit. After checking if there are unsaved changes, of course:

      quit:changed) Echo "Use 'save' or 'discard'" ;;
      quit:*) Echo "bye"; exit;;
"Open" and "new" check that there isn't a current file already, and set the current file to the file they open. The only difference between them is that "new" ignores the return value from the function open, while "open" gives an error message if the file could not be opened.
      open:open) Echo "There is a file open already" ;;
      open:*) if open "$arg"
	      then state=open; name="$arg"; $autoprint
	      else Echo "Cannot open $arg"
	      fi
	      ;;
      new:open) Echo "There is a file open already" ;;
      new:*)  open "$arg"
	      state=open
	      name="$arg"
	      $autoprint
	      ;;
The "close" command does not save changes. However, it will refuse to close a file if there are unsaved changes, so the user can call "save" or "discard"
      close:changed) Echo "Use 'discard' or 'save'" ;;
      close:closed) Echo "Closed already" ;;
      close:*) state=closed ;;
"save" and "discard" only need to check if there is an open file - it might be faster (less slow?) to avoid saving a file if there weren't unsaved changes, but I don't think it is worth the extra line and I'm too lazy to add it anyway. If "save" is called with an argument, it uses that name instead of the current file name. This is another reason to always save - you can implement a slow copy command calling the editor and redirecting the standard input from a file containing:
                 open source_file
		 save destination_file
		 quit
The code for these two operation is rather simple:
      save:closed) Echo "There isn't a file to save" ;;
      save:*) case "$arg" in
		?*) save "$arg" ;;
		*) save "$name" ;;
	      esac
	      state=open
	      ;;
      discard:changed) Echo "Your problem!"; state=closed ;;
      discard:*) state=closed ;;
The "name" commands just needs to change the editor's idea of the current file name:
      name:closed) Echo "No current file" ;;
      name:*) name="$arg" ;;
The line movement commands just need to call the functions supplied:
      goto:closed) Echo "No current file" ;;
      goto:*) goto "$arg"; $autoprint ;;
      next:closed) Echo "No current file" ;;
      next:*) next; $autoprint ;;
      prev:closed) Echo "No current file" ;;
      prev:*) prev; $autoprint ;;
The text manipulation commands just call the given functions. Note that $arg is unquoted in "replace" and "nreplace", which means that the shell will split the arguments for you - it also means that you can't have spaces in any argument except the last one.
      replace:closed) Echo "No current file" ;;
      replace:*) if rstring 1 $arg
		 then state=changed; $autoprint
		 else Echo "Not found"
		 fi
		 ;;
      nreplace:closed) Echo "No current file" ;;
      nreplace:*) if rstring $arg
		  then state=changed; $autoprint
		  else Echo "Not found"
		  fi
		  ;;
      delete:closed) Echo "No current file" ;;
      delete:*) delete; state=changed; $autoprint ;;
      insert:closed) Echo "No current file" ;;
      insert:*) insert "$arg"; prev; state=changed; $autoprint ;;
To print the current line, we use the function lineout we defined earlier; the commands "autoprint" and "noprint" just need to assign to the variable "autoprint".
      print:closed) Echo "No current file" ;;
      print:*) lineout ;;
      autoprint:*) autoprint="lineout" ;;
      noprint:*) autoprint="true" ;;
People who are paranoid about the system crashing on their editing might want to add:
      paranoid:*) autoprint='eval save "$name.autosave"' ;;
The command "paranoid" asks the editor to save the current file with the suffix .autosave appended after every command which might change the text, and even after most commands which cannot change it (but just in case...)