New Plan


When we get the Shift+Enter signal, what we have to do is to split the line containing the cursor. Everything to the left of the cursor stays on that line; everything after the cursor goes to the next line. This is a much simpler problem. To make this happen, I can envision code much like that earlier, that will split off the front and back of the line, replace the cursor line with the front, and insert just the back. The earlier code, though not clear, might work. However, I m going to throw that last code away and write it again, this time really programming by expressing my intention . To help with that, I ll figure out in words what I want to do:

  1. Get the front part of the line containing the cursor.

  2. Get the back part of the line containing the cursor.

  3. Replace the line containing the cursor with the front part.

  4. Insert the back part as the next line.

  5. Move the cursor to the right by the size of a newline.

OK, what does that code look like?

 public void InsertReturn() { 
string front = FrontOfCursorLine();
string back = BackOfCursorLine();
lines[LineContainingCursor()] = front;
lines.Insert(LineContainingCursor()+1, back);
selectionStart += Environment.NewLine.Length;
}

This time I did program my intention. Can you see that this code makes sense and follows the narrative plan I just described? We get the two line parts , replace the current line with the front, insert the back after it, and update the selection start. Looks good to me. Of course we haven t implemented the FrontOfCursorLine and BackOfCursorLine methods . I ll code one of those by intention as well:

 private string FrontOfCursorLine() { 
string line = lines[LineContainingCursor()];
int position = PositionOfCursorInLine();
return line.Substring(0, position);
}

Are you beginning to get the picture here? We get the line, we get the position of the cursor in the line, and we split off the front of the string. One part is left unsolved: PositionOfCursorInLine(). Our variable SelectionStart is the position of the cursor in all the lines, counting the newlines. So if we subtract that number from selectionStart, what s left will be the position in our line. It turns out that we have a method to return that number: SumLineLengths(), which looks like this:

 private int SumLineLengths(int cursorLine) { 
int length = 0;
for (int i = 0; i < cursorLine; i++)
length += ((String)lines[i]).Length + Environment.NewLine.Length;
return length;
}

We ll just implement PositionOfCursorInLine() by using that method, like this:

 private int PositionOfCursorInLine() { 
return selectionStart - SumLineLengths(LineContainingCursor() -1);
}

And then we ll build BackOfCursorLine similarly to FrontOfCursorLine:

 private string BackOfCursorLine() { 
string line = lines[LineContainingCursor()];
int position = PositionOfCursorInLine();
return line.Substring(position+1);
}

Now frankly, this is pretty scary. I ve built four methods in a row without a green bar. But I expect this to work, or at least to be close. Time to build and test. If this doesn t work pretty quickly, I m going to back the code out and proceed in smaller steps, but I might get away with it this time.

Well, the code doesn t compile because I forgot to cast the lines to string. Templates in C# can t come along fast enough for me. So the methods become

 private string FrontOfCursorLine() { 
string line = (string) lines[LineContainingCursor()];
int position = PositionOfCursorInLine();
return line.Substring(0, position);
}
private string BackOfCursorLine() {
string line = (string) lines[LineContainingCursor()];
int position = PositionOfCursorInLine();
return line.Substring(position+1);
}

And we try again...build succeeds, but the test fails. The message is telling me that in InsertPre, in FrontOfCursorLine, calling Substring, the length has to be inside the string. So the position is coming out too large. I m tempted to debug. Fortunately, I ve gone out of my way not to learn how to set a breakpoint and then run the NUnit tests. I ll probably ask someone how to do that later today, because I d really like to look around. But a better practice is probably to proceed in smaller steps. I ll leave those methods in, but I ll write a couple of tests against them. I ll have to declare them public to do that, which probably bothers you more than it does me. Let s talk about it later. Here s my first new test:

 [Test] public void CursorPosition() { 
model.SetLines (new String[] { "<P></P>", "<pre></pre>" });
model.SelectionStart = 12; // after <pre>
AssertEquals(5, model.PositionOfCursorInLine());
}

The test doesn t run. The message is Expected 5 but was 12. That trivial method PositionOfCursorInLine() must not be working ”what s up with that? Well, obviously it returned zero, to subtract from the 12. Clearly an off-by-one error in the calculation: we must be checking none of the lines. Sure enough. Look at SumLineLengths. It sums up to but not including its parameter line. And we have called it using LineContainingCursor() -1. I ll change that and things should work.

Wrong again! Expected 5 but was 3. I should probably give up, but I see the error: the test is wrong. The SelectionStart doesn t include the newline at the end of the first line: it should be 14. I ll fix that and that test will work.

Ha! Good, it works. The InsertPre test, however, is still failing. But look at it:

 [Test] public void InsertPre() { 
model.SetLines (new String[1] {"<P></P>"});
model.SelectionStart = 7;
model.InsertPreTag();
AssertEquals("<pre></pre>", model.Lines[1]);
AssertEquals(14, model.SelectionStart);
model.InsertReturn();
AssertEquals("<pre>", model.Lines[1]);
AssertEquals("", model.Lines[2]);
AssertEquals("</pre>", model.Lines[3]);
AssertEquals(16, model.SelectionStart);
}

I haven t updated it yet, and it s still expecting that blank line. The correct test is

 [Test] public void InsertPre() { 
model.SetLines (new String[1] {"<P></P>"});
model.SelectionStart = 7;
model.InsertPreTag();
AssertEquals("<pre></pre>", model.Lines[1]);
AssertEquals(14, model.SelectionStart);
model.InsertReturn();
AssertEquals("<pre>", model.Lines[1]);
AssertEquals(" < /pre > ", model.Lines[2]);
AssertEquals(16, model.SelectionStart);
}

And it almost works. The error says that the assert on Lines[2] fails, returning pre> , not <pre> . Of course. The BackOfCursorLine method extracts starting at position+1. That should say position. I think I ll beef up my simple test before fixing this obvious bug, as a lesson to myself to do these things correctly:

 [Test] public void CursorPosition() { 
model.SetLines (new String[] { "<P></P>", "<pre></pre>" });
model.SelectionStart = 14; // after <pre>
AssertEquals(5, model.PositionOfCursorInLine());
AssertEquals(" < pre > ", model.FrontOfCursorLine());
AssertEquals(" < /pre > ", model.BackOfCursorLine());
}

This test fails, because of the position+1 in BackOfCursorLine. Change that method to the following:

 public string BackOfCursorLine() { 
string line = (string) lines[LineContainingCursor()];
int position = PositionOfCursorInLine();
return line.Substring( position );
}

And all the tests run! Whew! Let s pause for a moment to [re]learn some lessons.




Extreme Programming Adventures in C#
Javaв„ў EE 5 Tutorial, The (3rd Edition)
ISBN: 735619492
EAN: 2147483647
Year: 2006
Pages: 291

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net