Introduction
I sometimes need to include source code in a document or report in order to walk the reader through that code. Invariably, I end up referring to a particular section of code as "starting from the 27th line from the top, ending at the 33rd line, inclusive" or some such convolution. It would be handy to be able to quickly and easily add line numbers to the particular code snippet, paste it into my document, and refer to "lines 27-33" instead.
The code in this little utility provides that functionality. You can copy a code snippet (or any text, in fact) into a RichTextBox
in the application, press a button to add line numbers, and the formatted, line-numbered code comes out the other end in the wink of an eye. This has saved me a lot of time and effort, particularly as I'm currently writing a book that includes a lot of code examples.
Background
Although the utility is presented as a simple application to which you can add additional bits and pieces as required, the guts of the line numbering functionality reside in a simple class that contains only two methods. As a result, this article concentrates on this class rather than on any other standard application functionality (Cut-Copy-Paste, etc.). Moreover, I haven't added any file I/O operations to the app (Open, Save, etc.) as personally I don't need it � but you may want to do that.
In addition, although the line numbering class is presented in the context of a Windows application, the relevant code can easily be used or adapted to provide similar functionality via a web application, web service or whatever.
Using the application
The screenshot above displays a lump of code prior to the addition of line numbers. The Options fields allow you to specify the following parameters:-
- Line Number Padding � allows you to specify the number of spaces that will separate the line number from the start of each converted line.
- Convert Tabs to Spaces checkbox � if checked, all tabs will be converted to spaces. This is useful for formatting purposes if the resulting code is to be copied to a word processing or DTP application and you don�t want to mess around with "complex" tab settings.
- Tab to Spaces Padding � this field allows you to specify how many spaces a tab is replaced with if the Convert Tabs to Spaces checkbox is checked.
The main text area contains the source text that is to be converted. You can paste your text into here. Converted text is placed back into this control.
To convert the text, press the "Add Line Numbers" button. The contents of the main text area will be replaced with the converted, line-numbered text. You can then copy and paste the converted text to another application as required. The screenshot below shows the converted text:
LineNumberBuilder Class
The main worker class contained in the downloads for this article is named LineNumberBuilder
. It contains four properties, one public method, and one private method. The properties (accessed using the usual "getters" and "setters") hold the values you set in the main window Options fields, as well as the source text you have pasted into the RichTextBox
. Here's a snapshot of the class (the getter & setter code has been removed from the listing):
1: public class LineNumberBuilder
2: {
3: private string inputText;
4: private int lineNumberPaddingWidth;
5: private bool convertTabsToSpaces;
6: private int tabToSpacesWidth;
7:
8:
9: public LineNumberBuilder()
10: {
11: }
12:
13: public void ConvertText(){...}
14:
15: private string GetFormattedLineNumber( int lineNumber,
int numberMaxWidth, string padding ){...}
16: }
The table below shows how the private class variables map to the public class properties.
Class Variable to Property Mapping |
Class Variable |
|
Class property |
inputText |
-> |
Text |
lineNumberPaddingWidth |
-> |
LineNumberPaddingWidth |
convertTabsToSpaces |
-> |
ConvertTabsToSpaces |
tabToSpacesWidth |
-> |
TabToSpacesWidth |
The public method, ConvertText()
, does the actual conversion of your source text and adds the formatted line numbers. The private method, GetFormattedLineNumber()
, which is called by ConvertText()
, creates and formats the line number for each line in the text.
Let's have a quick look at the methods in this class, starting with the GetFormattedLineNumber()
method. We will see how the class is used by the application later in the article.
The GetFormattedLineNumber() Method
This private method is called by the public ConvertText()
method. Its job is to build the line number text for a specific line. It returns the formatted line number to the calling routine as a string.
1: private string GetFormattedLineNumber( int lineNumber,
int maxNumberWidth, string padding )
2: {
3: StringBuilder line = new StringBuilder();
4:
5: line.Append( lineNumber.ToString().PadLeft( maxNumberWidth, ' ' ) );
6: line.Append( ":" );
7: line.Append( padding );
8:
9: return line.ToString();
10: }
The method has three parameters: the first contains the current line number we want to format; the second contains the length of the longest line number string - that is, if there are 200 lines in the source text, the length of the longest line number string is 3; the third parameter is a string that contains the padding we want to add - this separates the line number and colon from the start of the text for a line.
First of all, we create a local StringBuilder
object, line
, that is used to hold the line number text as it is constructed.
Line 5 appends the current line number to the line
object, left-padding it with spaces as specified in the maxNumberWidth
parameter. This ensures that the line number is right-justified. If you want it to be left-justified, change PadLeft
to PadRight
.
Line 6 appends the colon to the padded line number, and line 7 appends the additional padding - the user specifies the padding width using the "Line Number Padding" field in the main window Options.
Line 9 of this method simply returns the formatted line number text as a string.
The ConvertText() Method
This method processes the source text, inserting a line number at the beginning of each line.
1: public void ConvertText()
2: {
3: StringBuilder output = new StringBuilder();
4:
5: try
6: {
7: char[] end_of_line = {(char)10};
8: string[] lines = this.Text.Split( end_of_line );
9:
10: int line_count = lines.GetUpperBound(0)+1;
11: int linenumber_max_width = line_count.ToString().Length;
12: string padding = new String( ' ', this.LineNumberPaddingWidth);
13:
14: for ( int i=0; i<line_count; i++ )
15: {
16: output.Append( this.GetFormattedLineNumber( i+1,
linenumber_max_width, padding ) );
17: output.Append( lines[i] );
18: output.Append( "\r\n" );
19: }
20:
21: if ( this.ConvertTabsToSpaces )
22: {
23: string spaces = new String( ' ', this.TabToSpacesWidth);
24: output = output.Replace( "\t", spaces );
25: }
26: }
27: catch ( Exception e )
28: {
29: output.Append( e.Message );
30: }
31:
32: this.Text = output.ToString();
33: }
Line 3 creates a new StringBuilder
instance that we use to store the converted text as we build the individual lines.
Lines 7-8 take the source text (which is contained in the Text
property of the class instance) and splits it into a string array � one array item for each line of text. The end_of_line
variable is used to specify the character(s) that we want to split the text on. In this case, it uses the \n
newline character (ASCII value 10). (As homework, you might want to allow the user to specify this delimiter, perhaps as a field/property in the main window Options fields.)
Line 10 simply counts the number of items in the string array. Line 11 converts line_count
to a string and gets its length. This is used to correctly pad the line numbers so that unwanted, ugly indentation is avoided and the colons that separate the line number from the line text line up vertically in the correct manner. Line 12 creates a new string that holds the padding spaces that separate the line number from the start of the line text.
Lines 14-19 perform the main formatting action in a for
loop, processing the source text line by line.
Within the for
loop, line 16 appends the line number to the output
object. It calls the private method GetFormattedLineNumber()
which creates the line number and adds the trailing colon and any necessary padding.
Line 17 appends the source text line (contained in the referenced string array item lines[i]
).
Lines 18 simply adds an end-of-line character to the line so that the original line breaks are preserved when the conversion is completed.
If the user has elected to convert tabs to spaces, lines 21-25 take care of this. A new string is created containing the number of spaces specified by the user via the "Tab to Spaces Padding" field in the main window Options. Any tab characters contained in the output
object are then replaced with the contents of the spaces
string.
Finally, the converted text is placed in the Text
property of the class instance (line 32).
Using the LineNumberBuilder class
When the user presses the "Add Line Numbers" button in the main window, the button's click event makes a call to a routine named ExecuteLineNumbering()
. Here's what this routine looks like:
1: private void ExecuteLineNumbering()
2: {
3: if ( this.SourceMemo.Text.Length > 0 )
4: {
5: LineNumberBuilder builder = new LineNumberBuilder();
6:
7: builder.Text = this.SourceMemo.Text;
8: builder.LineNumberPaddingWidth =
Convert.ToInt32( this.eLineNumberPadding.Text );
9: builder.ConvertTabsToSpaces = this.cbConvertToSpaces.Checked;
10: builder.TabToSpacesWidth =
Convert.ToInt32( this.eTabPadding.Text );
11:
12: builder.ConvertText();
13: this.SourceMemo.Text = builder.Text;
14: }
15: }
First of all, we check (in line 3) to see if there's any text in the SourceMemo
RichTextBox
. If there is, we create a new instance of the LineNumberBuilder
class (builder
). In lines 7-10, we then populate the properties of this object with the data contained in the main window fields, including the contents of the RichTextBox
. (To aid code readability, and out of sheer idleness, no error-checking is provided to trap invalid field values � this is left for you to do as homework.)
Finally, in line 12, we call the builder.ConvertText()
method to number the lines. In line 13, the converted lines - now returned in our builder.Text
property - are placed into the RichTextBox
Text
field, replacing the text that was originally pasted there.
Points of Interest
As you can see from the formatted code above, this handy little utility makes the job of explicating code in a book or a document very much easier to do. And it avoids having to pepper body text with code snippets which may be difficult to follow out of context. I certainly find it easier to follow code explanations when line numbers are referred to by an author.
Despite using a RichTextBox
, this application doesn't handle rich-text formatting. There are two reasons for this: firstly, the documents/books I write don't really need or use syntax-highlighted text; secondly, the code required to properly handle RTF is much more complex and involves getting one's head around the Rich Text Format documentation. This is way out of scope for this article. This might seem a cop-out but, in this utility, the aim is to demonstrate a simple technique in an uncluttered way. If you want to preserve all of the font characteristics of your text, you should investigate this further on your own.
Finally, the demo project was built using Visual Studio .NET 2003. If you are using an earlier version of VS.NET, just use the source code as there is nothing in the LineNumberBuilder
class that is specific to or dependent on VS.NET 2003.
History
This is the first (and only) version of this application... unless it breaks, in which case it's v0.1.1 Alpha.