HEX
Server: Apache
System: Linux pdx1-shared-a1-38 6.6.104-grsec-jammy+ #3 SMP Tue Sep 16 00:28:11 UTC 2025 x86_64
User: mmickelson (3396398)
PHP: 8.1.31
Disabled: NONE
Upload Files
File: //usr/share/jed/lib/ashell.sl
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% async shell for jed
ifnot (is_defined ("Shell_Default_Shell"))
{
#ifdef WIN32
   variable Shell_Default_Shell = getenv ("COMSPEC");
   if (Shell_Default_Shell == NULL)
     Shell_Default_Shell = "cmd.exe";
#else
   variable Shell_Default_Shell = getenv ("SHELL");
   if (Shell_Default_Shell == NULL)
     Shell_Default_Shell = "sh";
#endif
}

ifnot (is_defined ("Shell_Default_Interactive_Shell"))
{
#ifdef WIN32
   variable Shell_Default_Interactive_Shell = "";
#else
   variable Shell_Default_Interactive_Shell = "-i";
#endif
}

variable AShell_Id = -1;

private variable Current_Working_Directory = ".";

public define ashell_expand_path (path)
{
   if ((path == NULL) or (path == "~"))
     path = "~/";

#ifdef UNIX
   if (path[0] == '~')
     {
	variable user = extract_element (path, 0, '/');
	if (strlen (user) > 1)
	  {
	     variable dir;
	     (dir,,,,) = get_passwd_info (user[[1:]]);
	     if (strlen (dir))
	       (path,) = strreplace (path, user, dir, 1);
	  }
     }
#endif

   variable cwd = getcwd ();
   () = chdir (Current_Working_Directory);
   path = expand_filename (path);
   () = chdir (cwd);
   return path;
}

public define ashell_getcwd ()
{
   return Current_Working_Directory;
}

public define ashell_chdir (dir)
{
   dir = ashell_expand_path (dir);
   if (-1 == chdir (dir))
     verror ("chdir %s failed: %s", dir, errno_string (errno));

   Current_Working_Directory = dir;
}

private define builtin_cd (cmd, argc, argv)
{
   variable arg = ashell_expand_path (argv[1]);

   if (0 == chdir (arg))
     Current_Working_Directory = arg;

   return cmd;
}

private define builtin_edit (cmd, argc, argv)
{
   variable dir = Current_Working_Directory;

   foreach (argv[[1:argc-1]])
     {
	variable file = ();

	() = find_file (expand_filename (dircat (dir, file)));
     }

   return "";
}

private define builtin_most (cmd, argc, argv)
{
   ERROR_BLOCK
     {
	_clear_error ();
	insert ("\nUnable to read file.\n");
	return "";
     }

   if (argc < 2)
     return cmd;

   () = read_file (argv[1]);
   pop2buf (whatbuf ());
   most_mode ();

   return "";
}

private variable Builtin_Cmds = Assoc_Type [Ref_Type];

public define ashell_add_builtin (cmd, fun)
{
   Builtin_Cmds[cmd] = fun;
}

ashell_add_builtin ("cd", &builtin_cd);
ashell_add_builtin ("edit", &builtin_edit);
ashell_add_builtin ("jed", &builtin_edit);
ashell_add_builtin ("most", &builtin_most);
ashell_add_builtin ("more", &builtin_most);
ashell_add_builtin ("less", &builtin_most);

private define parse_shell_cmd (cmd)
{
   variable argv, argc;

   cmd = strtok (cmd);
   argc = length (cmd);

   % This has the effect of NULL terminating the argv list
   argv = String_Type[argc+1];

   %  remember that in slang1 [0:-1] picks out elements in an array indexing
   %  context.  So, only do this if argc is non-zero.  slang2 has different
   %  semantics and does not suffer from this problem.
   if (argc)
     argv[[0:argc-1]] = cmd;

   return (argc, argv);
}

private define try_builtin (cmd)
{
   variable argc, argv;

   (argc, argv) = parse_shell_cmd (cmd);
   ifnot (argc) return cmd;

   variable fun, command;

   command = argv[0];
   ifnot (assoc_key_exists (Builtin_Cmds, command))
     return cmd;

   fun = Builtin_Cmds[command];
   return @fun (cmd, argc, argv);
}

define ashell_send_input ()
{
   variable buf;
   variable this_line, mark_line;
   variable m, ch, prompt;

   m = process_mark (AShell_Id);

   this_line = what_line ();
   push_mark ();
   goto_user_mark (m);
   mark_line = what_line ();

   if (this_line >= mark_line)
     {
	pop_mark_0 ();
	push_mark_eob ();
	buf = bufsubstr ();
     }
   else
     {
	bskip_chars ("\t ");
	push_mark ();
	ifnot (bolp ())
	  {
	     go_left_1 ();
	     ch = what_char ();
	  }
	bol ();
	prompt = bufsubstr ();
	pop_mark_1 ();
	bol ();
	if (looking_at (prompt))
	  {
	     go_right (strlen (prompt));
	  }
	else if (ffind_char (ch))
	  {
	     go_right_1 ();
	  }
	push_mark_eol ();
	buf = bufsubstr ();
	eob ();
	insert (buf);
     }
   newline ();
   move_user_mark (m);

   buf = try_builtin (buf);

#ifdef WIN32
   send_process (AShell_Id, buf + "\n\r");
#else
   send_process (AShell_Id, buf + "\n");
#endif
}

define ashell_send_intr ()
{
   signal_fg_process (AShell_Id, 2);   %  SIGINT
}

define ashell_completion ()
{
   variable partial_completion;
   variable dir, file;

   push_spot ();
   push_mark ();
   bskip_chars ("^ \n\t'`\"><$");

   partial_completion = bufsubstr();
   pop_spot ();

   (dir, file) = parse_filename (partial_completion);
   dir = ashell_expand_path (dir);

   variable len = strlen (file);
   variable files = listdir (dir);
   files = files[where (0 == array_map (Int_Type, &strncmp, files, file, len))];

   variable num_matches = length (files);
   if (num_matches == 0)
     {
	message ("No completions");
	return;
     }

   variable match;

   variable i;
   _for (0, num_matches-1, 1)
     {
	i = ();
	match = files[i];
	if (2 == file_status (path_concat (dir, match)))
	  files[i] = path_concat (match, "");   %  add /
     }

   match = files[0];
   if (num_matches == 1)
     {
	insert (match[[len:]]);
	return;
     }

   % Complete as much as possible.  By construction, the first len characters
   % in the matches list are the same.  Start from there.
   _for (len, strlen (match)-1, 1)
     {
	i = ();
	variable try_match = match[[0:i]];
	if (num_matches != length (where (0==array_map (Int_Type, &strncmp,
							try_match, files, i+1))))
	  {
	     if (i != len)
	       insert (match[[len:i-1]]);
	     break;
	  }
     }

   variable cbuf = whatbuf ();
   pop2buf ("*completions*");
   erase_buffer ();
   foreach (files)
     {
	insert (());
	newline();
     }

   buffer_format_in_columns ();
   bob ();
   pop2buf (cbuf);
   message ("Ambiguous Completions");
}

$1 = "AShellMap";
ifnot (keymap_p ($1)) make_keymap ($1);
definekey ("ashell_send_input", "^M", $1);
undefinekey ("^C", $1);
definekey ("ashell_send_intr", "^C", $1);
definekey ("ashell_completion", "\t", $1);

define ashell_signal_handler (pid, flags, status)
{
   variable msg;

   eob ();
   msg = aprocess_stringify_status (pid, flags, status);
   vinsert ("\n\n----- %s ------\n\n", msg);
   AShell_Id = -1;
}

define ashell_insert_output (pid, str)
{
   goto_user_mark (process_mark (pid));
   variable use_overwrite = not eolp ();
   foreach (str) using ("bytes")
     {
	variable ch = ();
	if (ch == '\r')
	  {
	     bol ();
	     use_overwrite = 1;
	     continue;
	  }
	if (ch == 8)
	  {
	     ifnot (bolp ())
	       go_left(1);
	     use_overwrite = 1;
	     continue;
	  }
	if (ch == '\n')
	  {
	     eol ();
	     newline ();
	     use_overwrite = 0;
	     continue;
	  }

	if (use_overwrite) del ();
	insert_byte (ch);
     }
   variable col = what_column ();
   eol_trim ();
   goto_column (col);
   move_user_mark (process_mark (pid));
}

define ashell ()
{
   variable buf = "*ashell*";
   variable arg, nargs = 0;

   if ((AShell_Id != -1) and bufferp (buf))
     {
	pop2buf (buf);
	error ("Currently, only one shell process is supported.");
     }

   pop2buf (buf);
   (,Current_Working_Directory,,) = getbuf_info ();
   () = chdir (Current_Working_Directory);

   use_keymap ("AShellMap");
   run_mode_hooks ("ashell_mode_hook");
   erase_buffer ();
#iffalse
   AShell_Id = open_process (Shell_Default_Shell,
			     Shell_Default_Interactive_Shell, 1);
#else
   % parse possible arguments
   forever
     {
	arg = extract_element (Shell_Default_Shell, nargs, ' ');
	if (arg == NULL)
	  break;

	nargs++;
	arg;		% push on stack
     }

   Shell_Default_Interactive_Shell; nargs;% push on stack
   AShell_Id = open_process ();
#endif
   set_process (AShell_Id, "signal", "ashell_signal_handler");
   set_process (AShell_Id, "output", "ashell_insert_output");
}

provide ("ashell");