
(*
 * Edit expressions, input lines and definitions:
 *	Edit[expr___] allows editing of the values of expressions ``expr''.
 *	EditIn[line_Integer:$Line-1] allows one to edit input lines ``line''.
 *	EditDefinition[sym___Symbol] allows editing of the rules attached to ``sym''.
 * All of these functions use the environment variable EDITOR to determine
 * what editor to use, and default to vi.
 *
 *)


(*
 * Global objects.
 *)
Begin["System`"]

Edit::usage = "Edit[expr___] lets you edit the expressions expr."

EditIn::usage = "EditIn[n__Integer] lets you edit input lines n.
	EditIn[] edits the previous input line."

EditDefinition::usage = "EditDefinition[syms___] lets you edit the rules
	attached to the symbols syms."
	 
EditDef::usage = "EditDef is the obsolete name of EditDefinition."
Begin["EditPrivate`"]

(*
 * Just edit a bunch of expressions.
 *)

Edit[expr___] :=
	Block[{
		tmpfile = OpenTemporary[]
		},
		Scan[
			Write[tmpfile, #]&,
			{ expr }
		];
		Close[tmpfile];
		EditIO[tmpfile]
	]

EditString[expr___] :=
	Block[{
		tmpfile = OpenTemporary[]
		},
		Scan[
			WriteString[tmpfile, #]&,
			{ expr }
		];
		Close[tmpfile];
		EditIO[tmpfile]
	]


(*
 * Edit an input line.
 *)

EditIn[] := EditIn[$Line - 1]

(*
EditIn[line_Integer] := EditInAux[ EditString[ InString[ line]]]
*)

EditIn[line_Integer] := EditString[ InString[ line]] /; line < $Line

EditIn[lines__Integer] :=
		Last[ Map[ EditIn, List[ lines]]]
(*
EditInAux[ x_String] := ToExpression[x]
EditInAux[ x_] := x 
*)

EditInAux = ToExpression

Attributes[EditDefinition] = HoldAll

EditDefinition[syms___] :=
	Block[{
		tmpfile = OpenTemporary[],
		heldsyms = Map[ Hold, Hold[{syms}], {2} ][[1]]
		},
		Scan[ Apply[ WriteDef, # ]&, heldsyms ];
		Close[tmpfile];
		EditIO[tmpfile]
	] /; AllSyms[syms]

EditDef = EditDefinition

(*
 * WriteDef writes the definition of a symbol to the file tmpfile
 * (passed via dynamic scoping).
 *)
Attributes[WriteDef] = HoldAll

WriteDef[sym_Symbol] :=
	Block[{
		protected = MemberQ[Attributes[sym], Protected],
		locked = MemberQ[Attributes[sym], Locked],
		symname = ToString[HoldForm[InputForm[sym]]]
		},
		WriteString[tmpfile, "	(* ", symname, " *)\n" ];
		If[protected || locked,
			WriteString[tmpfile, "(*\n",
				" * Warning: ", symname, " is ",
				If[protected,
					If[locked,
						"Protected and Locked.\n",
						"Protected.\n"
					],
					"Locked.\n"
				],
				" * The definition is commented out:\n\n"
			];
			If[protected,
				WriteString[tmpfile,
					"Unprotect[", symname, "]\n"
				]
			];
		];
		WriteString[tmpfile,
			"Clear[", symname, "]\n"
		];
		Write[tmpfile, Definition[sym]];
		If[protected || locked,
			WriteString[tmpfile,
				"\n * End of commented definition of ",
				symname,
				".\n",
				" *)\n"
			]
		];
		WriteString[tmpfile, "\n"]
	]


(*
 * AllSyms tests to make sure that all of its arguments are symbols.
 * Note, this is a bit tricky since the symbols may have values.
 *)
Attributes[AllSyms] = HoldAll

AllSyms[syms___] :=
	Block[{
		tmp
		},
		tmp = Map[ Hold, Hold[{syms}], {2} ][[1]];
		tmp = Select[ tmp, ! MatchQ[#, Hold[_Symbol]]& ];
		Scan[
			Message[ EditDefinition::edsym,
				 Apply[ HoldForm, # ] ]&, tmp ];
		Length[tmp] == 0
	]

EditDefinition::edsym = "`1` is not a symbol."


(*
 * Support routines for editing.
 *)


(*
 * Edit a file (re-editing on errors), reading it into In/Out, and then
 * remove the file.  The result is the result of evaluating the last
 * expression.
 *)
EditIO[file_String] := (
	While[
		Run["${EDITOR-vi} ", file];
		And[
			! ReadIO[file],
			TrueQ[GetYesNo["Re-edit? "]]
		]
	];
	Run["rm ", file];
	Out[--$Line]
)

EditIO[streamhead_Symbol[name_String, serialnumber_Integer]] := EditIO[name]


(*
 * StreamFileName[stream] returns the filename part of a stream indicator,
 * and works in either 1.2 or 2.0. 
 *)

StreamFileName[streamhead_Symbol[name_String, serialnumber_Integer]] := name

StreamFileName[name_String] := name


(*
 * GetYesNo prompts the user and reads in either yes (return True), no
 * (return False) or EndOfFile (return EndOfFile).
 *)
GetYesNo[prompt_] :=
	Block[{
		resp
		},
		While[True,
			resp = InputString[prompt];
			If[MemberQ[{"y", "yes", "Y", "Yes"}, resp],
				Return[True],
			If[MemberQ[{"n", "no", "N", "No"}, resp],
				Return[False],
			If[SameQ[resp, EndOfFile],
				Return[EndOfFile]
			]]]
		]
	]


(*
 * Open a file, read it in setting In and Out and then close it.
 * If False is returned, it means that there was a syntax error,
 * otherwise True is returned.
 *)
ReadIO[filename_String] :=
	Block[{
		value,
		result,
		stream
		},
		stream = OpenRead[filename];
		If[! SameQ[StreamFileName[stream], filename],
			Return[True]	(* the file no longer exists *)
		];
		While[
			value = Check[
					Read[
						stream,
						Hold[Expression]
					],
					"Error"
			];
			Switch[value,
				Hold[_],
					Apply[EvalIO, value];
					True,
				EndOfFile,
					result = True;
					False,
				"Error",
					result = False;
					False
			]
		];
		Close[stream];
		result
	]


(*
 * EvalIO causes its unevaluated argument to be the value of the current
 * input line and its evaluated argument to be the value of the current
 * output line.  It then increments the current line number.
 *)
Attributes[EvalIO] = HoldAll

EvalIO[arg_] :=
	Block[{
		prot,
		value
		},
		prot = Unprotect[{"In", "Out"}];
		In[$Line] := arg;
		Out[$Line++] = arg;
		Scan[Protect, prot];
	]

(*
 * Protect all functions.
 *)
Protect[{ Edit, EditIn, EditDefinition, WriteDef, AllSyms, EditIO,
		GetYesNo, ReadIO, EvalIO, StreamFileName }];


End[]

End[];
