⑨ lab ≡ ByteLabs

Plan9/Sam

— Igor Böhm

Sam is an interactive multi-file text editor. In addition to a mouse-driven cut-and-paste interface, a textual command language makes complex or repetitive editing tasks easy to specify. The command language is characterized by the composition of regular expressions to describe the structure of the text being modified.

A normal sam session begins with a new sam terminal window. You are expected to use the console at the top and mouse chording to manipulate files and edit text. sam behaves pretty much like ed if you invoke it with sam -d.

This post assumes you have read The Sam Paper and are running on 9front.

To switch from a text window to the end of the command window use ctrl+b. Ctrl+g switches to the last focused window. This is useful to return to a text window after having switched to the command window.

Manipulating Files

To open all source, header, and GN build files located in src and inc folders run the following command in a rio rc window:

% cd $home/NEIN999/
% sam `{walk -f src inc | grep '\.(c|cpp|cc|h|gn)'}

The above missed to add make files to the list of files sam knows about:

B < walk -f src | grep '(Makefile|mkfile)'

Note: the previous command is executed in the ‘samterm’ window.

To see what files are known to sam you can either right click or type n into a samterm window to get a list of files (sam is a multi-file editor so it keeps track of all files it was told to open).

Large scale software projects contain hundreds of files organised in more or less complex file hierarchies. There might be so many files that selecting a file using the mouse is cumbersome. It might be quicker to search for files using sams command language.

To see all .cpp files known to sam that are in a util folder one can use the following:

X/- .*\/util\/.*\.cpp/

That will print:

 -. src/util/Allocate.cpp
 -. src/util/Arguments.cpp
 -. src/util/Atomic.cpp
 ...

To open one of the files prepend the file name with b, select b src/util/Allocate.cpp, middle-click and chose send.

It is easy to get into a frenzy and modifiy a multitude of files. To check what files have been modified the following command is useful:

X/'/

In this particular example it prints:

'+. src/util/Allocate.cpp

Especially after bulk search and replace actions many files might be modified. To save all modified files to disk run the following:

X/'/ w

Commands that output file names usually prepend the name with a few characters. The first three characters are a concise notation for the state of the file:

Encoding Description
. indicates the current file
+ the file has a window open
- the file has been read by sam
* the file is open in more than one instance
' the loaded file differs from the file on disk

Search and Replace

Let’s check what files have a copyright notice and print some context to check the year, company name, and the presence of a specific tag before for all source files in the util folder:

X/.*(inc|src)\/util\/.*\.(cc|cpp|h)/ ,g/Copyright \(C\)/ {
	,x/.*\n/ g/Copyright \(C\)/ {
		f
		=
		-1,+1 p
	}
}

This command executed in the samterm window produces the following output:

 -. inc/util/Adaptor.h
3; #16,#83
 <copyright>
 Copyright (C) 2013 PlanNein, Inc. This software and the associate
 documentation are confidential and proprietary to PlanNein, Inc.
 -. inc/util/Allocate.h
9; #301,#368
 <copyright>
 Copyright (C) 2013 PlanNein, Inc. This software and the associate
 documentation are confidential and proprietary to PlanNein, Inc.
...

Need more context before or after the match, similar to UNIX style grep -C2 ... or grep -B2 -A3 ...?

X/.*(inc|src)\/util\/.*\.(cc|cpp|h)/ ,g/Copyright \(C\)/ {
	,x/.*\n/ g/Copyright \(C\)/ {
		f
		=
		-2,+3 p
	}
}

Note how the output now shows two lines before the match and three lines after the matches (i.e. like grep -B2 -A3 ...:

 -. inc/util/Adaptor.h
3; #16,#83
/*
 <copyright>
 Copyright (C) 2013 PlanNein, Inc. This software and the associate
 documentation are confidential and proprietary to PlanNein, Inc.
 Your use or disclosure of this software is subject to the terms and
 conditions of a written license agreement between you, or your company,
 -. inc/util/Allocate.h
9; #301,#368
/*
 <copyright>
 Copyright (C) 2013 PlanNein, Inc. This software and the associate
 documentation are confidential and proprietary to PlanNein, Inc.
 Your use or disclosure of this software is subject to the terms and
 conditions of a written license agreement between you, or your company,
 ...

Replace

Replacing stuff in a single file is easy. Doing it across multiple files is much more interesting. Let’s say we want to replace all occurrences of Copyright (C) in source files with COPYRIGHT ©:

X/\.(cc|cpp|h)/ ,g/Copyright \(C\)/ {
	,x/.*\n/ g/Copyright \(C\)/ {
		s/Copyright \(C\)/COPYRIGHT ©/
	}
}

Let’s break this down:

  1. X/\.(cc|cpp|h)/… selects all source files ending in .(cc|cpp|h) that sam knows about
  2. ,g/Copyright \(C\)/… out of that set only considers files matching the pattern Copyright \(C\)
  3. ,x/.*\n/… for each line of such a file
  4. g/Copyright \(C\)/… that contains the pattern Copyright \(C\)
  5. { s/Copyright \(C\)/COPYRIGHT ©/ }… substitute the replacement text for the first match, in dot, of the regular expression.

You can do much more advanced things than simple search replace once you get the hang how to use the command language.

Cheatsheet

⑨ lab ≡ who cares…

Addresses

n,m line n to line m
address mark, see k below
. correct selection/position
0 correct selection/position
ˆ start of file
$ end of line/file
, equivalent to 0,$

Regular Expressions

. any character
* 0 or more of previous
+ 1 or more of previous
[ˆn] correct selection/position
[nm] n or m
(re) tag pattern
# substitute #’th tagged pattern

Text Commands

-/re/ search backward
+/re/ search forward
/re/ search in same direction as last
a/text/ Append text after dot
c/text/ Change text in dot
i/text/ Insert text before dot
d Delete text in dot
s/regexp/text/ Substitute text for regexp in dot
m address Move dot to after address
t address Copy dot to after address

Display Commands

p Print contents of dot
= Print value of dot
n Print file menu list

I/O Commands

n Print list of read and loaded files
b file-list Set current file to first in menu list
B file-list As b, but load new file-list
D file-list Delete named buffers
e [file-name] Replace current with file
r file-name Replace dot by contents of file
w file-name Write current to named file
f [file-name] Set/Change current file name
< command Replace dot by stdout of command
> command Send dot to stdin of command
| command Pipe dot through command
! command Run the command

Loops and Conditionals

x/regexp/ command Set dot and run command on each match
x cmd Set dot and run command on each matching line
y/regexp/ command as x but select unmatched text
X/regexp/ command Run command on files whose menu line matches
Y/regexp/ command As X but select unmatched files
g/regexp/ command If dot contains regexp, run command
v/regexp/ command If dot does not contain, run command

Miscellaneous

k Set address mark to value of dot
q Quit
u n Undo last n (default 1) changes
{ } Braces group commands
<compose> Xnnnn Insert char xxxx hex (Unix/Plan9)

Idioms

X/.*/,x/<cr>/d strip from all files
x/ˆ/ .,/0d strip C comments from selection
-/ˆ/+#10 goto the 10th colum in the current line
-0+,+0- round dot down to whole lines only
,x/ +/ v/ˆ/ c/ / compress runs of spaces, leaving indentation
s/"([ˆ"]*)"/‘‘1’’/ replace “hello” with ‘‘hello’’ in selection
s/.*/*&*/ enclose dot with asterisk
f <nl> set current file-name to null
> echo "" insert ascii code xxx at current pos
, > wc -l count lines in file
/text/+-p highlight the lines containing
-/text/ search for text backwards
$-/text/ search for the last occurrence of text in file
,x/<text>/+-p grep for text
.x/<pat>/ c/<rep>/ search for and replace with
B < echo *.c add all the C files in current dir to file list
B < grep -l <pat> * add all the files containing to file list
X/’/w write all modified files
Y/.c/D remove all non C files from file list
| fmt pipe selection through the text formatter
> mail <user> send selection as email to
x/0 a/0 double space selection
x/ˆ/ a/ / indent selection 1 tab
x/ˆ<tab>/d remove 1 tab of indent from selection
/(.+0+/ matches blocks of text separated by blank lines
! date get current date in sam window
,< wc push file into wc, count appears in sam window
0 < date insert date at start of file
1 < date replace first line with todays date
X D remove out all up-to-date files
,| sort sort current file
,x/ˆTODAY$/ < date replace TODAY with the output of date
,x/Plan9/|tr a-z A-Z replace all instances of Plan9 with upper case
,t "junk.c" 0 copy current file to start of junk.c
-/.PP/,/.PP/- highlight current paragraph in an nroff doc

#Plan9 #Dev #9front