commands/fed/src/lrtextview.cpp
changeset 0 7f656887cf89
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/fed/src/lrtextview.cpp	Wed Jun 23 15:52:26 2010 +0100
@@ -0,0 +1,1120 @@
+// lrtextview.cpp
+// 
+// Copyright (c) 2009 - 2010 Accenture. All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+// 
+// Initial Contributors:
+// Accenture - Initial contribution
+//
+
+#include "lrtextview.h"
+#include <e32cons.h>
+#include "filebuffer.h"
+#include <fshell/common.mmh>
+#include <fshell/ltkutils.h>
+
+const TInt KLastCtrlChar = 0x1f;	//Last control char in ASCII
+const TInt KCtrlHTab = 0x09;		//Horizontal Tab
+
+CLRTextView* CLRTextView::NewL(MConsoleProvider& aConsoleProvider, CFedBufferBase& aBuffer)
+	{
+	CLRTextView* self = new (ELeave) CLRTextView(aConsoleProvider, aBuffer);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+CLRTextView::~CLRTextView()
+	{
+	delete iLineData;
+	delete iMarkFlasher;
+	}
+
+CLRTextView::CLRTextView(MConsoleProvider& aConsoleProvider, CFedBufferBase& aBuffer)
+	: CTextView(aConsoleProvider, aBuffer), iMarkDocPos(-1), iOldNextLine(-1)
+	{
+	}
+
+void CLRTextView::ConstructL()
+	{
+	iLineData = new(ELeave) CLineData;
+	CTextView::ConstructL();
+	iMarkFlasher = CPeriodic::NewL(CActive::EPriorityStandard);
+	}
+
+//MDeferredClose
+TBool CLRTextView::TryCloseL()
+	{
+	if (!iBuffer.Modified())
+		{
+		return ETrue; // Ok to close now
+		}
+	else
+		{
+		TKeyCode ch = iConsoleProvider.Query(_L("Save changes to file? (yes/no/cancel) "), _L("ync\x1B"));
+		if (ch == 'c' || ch == EKeyEscape) return EFalse;
+		if (ch == 'y')
+			{
+			TInt err = Save();
+			if (err) return EFalse; // Don't allow exit if the save failed
+			}
+		return ETrue;
+		}
+	}
+
+TInt CLRTextView::Save()
+	{
+	if (!iBuffer.Editable() || iLineIsErrorMessage) return KErrNotSupported;
+	
+	CancelMark();
+	TBool wasOpen = iBuffer.IsOpen();
+	TInt err = KErrNone;
+	const TDesC& name = iBuffer.Title();
+	if (name.Length())
+		{
+		iConsoleProvider.InfoPrint(_L("Saving"));
+		TRAP(err, iBuffer.SaveL(name, ETrue));
+		HandleSaveResult(err, wasOpen, iBuffer.Title());
+		return err;
+		}
+	else
+		{
+		return SaveAs();
+		}
+	}
+
+TInt CLRTextView::SaveAs()
+	{
+	if (!iBuffer.Editable() || iLineIsErrorMessage) return KErrNotSupported;
+	
+	CancelMark();
+	TBool wasOpen = iBuffer.IsOpen();
+	TFileName name = iBuffer.Title();
+	TInt err = KErrCancel;
+	TBool go = iConsoleProvider.QueryFilename(_L("Save as: "), name);
+	if (go)
+		{
+		iConsoleProvider.InfoPrint(_L("Saving"));
+		TRAP(err, iBuffer.SaveL(name, EFalse));
+		if (err == KErrAlreadyExists)
+			{
+			if (iConsoleProvider.Query(_L("File already exists. Replace? (yes/no) "), _L("yn")) == 'y')
+				{
+				iConsoleProvider.InfoPrint(_L("Saving"));
+				TRAP(err, iBuffer.SaveL(name, ETrue));
+				}
+			}
+		HandleSaveResult(err, wasOpen, iBuffer.Title());
+		}
+	return err;
+	}
+
+void CLRTextView::HandleSaveResult(TInt aError, TBool aWasOpen, const TDesC& aName)
+	{
+	if (aError == KErrNone)
+		{
+		TRAP_IGNORE(DoRedrawL()); // Not only can QueryFilename mess up the screen, saving the file could change the line endings and thus change the character counts, meaning we need to recalculate & redraw
+		iConsoleProvider.InfoPrint(_L("Save succeeded"));
+		}
+	else if (aWasOpen && !iBuffer.IsOpen())
+		{
+		// An error occurred after the original file was closed. This is fatal so we need to put up a dialog to that effect
+		iLine.Format(_L("Temp file @ %S"), &aName);
+		iLineIsErrorMessage = ETrue;
+		UpdateStatus();
+		iConsoleProvider.InfoPrint(_L("Failed to rename temp file (%d)"), aError);
+		}
+	else
+		{
+		// Failed to save, but we didn't mess with the original so we can carry on using it
+		TRAP_IGNORE(DoRedrawL()); // Not only can QueryFilename mess up the screen, saving the file could change the line endings and thus change the character counts, meaning we need to recalculate & redraw
+		switch (aError)
+			{
+			case KErrDiskFull:
+				iConsoleProvider.InfoPrint(_L("Disk full, couldn't save file"));
+				break;
+			case KErrNoMemory:
+				iConsoleProvider.InfoPrint(_L("Out of memory!"));
+				break;
+			case KErrPathNotFound:
+				iConsoleProvider.InfoPrint(_L("KErrPathNotFound. Directory doesn't exist?"));
+				break;
+			case KErrGeneral:
+				iConsoleProvider.InfoPrint(_L("Couldn't save file. No idea why"));
+				break;
+			case KErrBadName:
+				iConsoleProvider.InfoPrint(_L("Bad file name"));
+				break;
+			case KErrAccessDenied:
+				iConsoleProvider.InfoPrint(_L("Access denied. Read-only file or disk?"));
+				break;
+			default:
+				iConsoleProvider.InfoPrint(_L("Unable to save file: %d"), aError);
+				break;
+			}
+		}
+	}
+
+//MKeyConsumer
+TBool CLRTextView::ConsumeKeyL(const TKeyCode& aCode)
+	{
+	TBool handled = ETrue;
+	switch(aCode)
+		{
+		case EKeyUpArrow:
+			MoveCursor(0, -1); break;
+		case EKeyDownArrow:
+			MoveCursor(0, 1); break;
+		case EKeyLeftArrow:
+			MoveCursor(-1, 0); break;
+		case EKeyRightArrow:
+			MoveCursor(1, 0); break;
+		case EKeyPageUp:
+		case CTRL('u'):
+			MoveCursor(0, -iWindow.iHeight); break;
+		case EKeyPageDown:
+		case CTRL('d'):
+			MoveCursor(0, iWindow.iHeight); break;
+		case EKeyHome:
+			UpdateCursor(TPoint(0, iCursor.iY));
+			break;
+		case CTRL('t'):
+			iCursor.SetXY(0, 0);
+			RequestData(ETrue, 0); break;
+		case CTRL('b'):
+			GoToLine(KMaxTInt); break;
+		case EKeyEnd:
+			{
+			TInt pos = iLineData->LastValidPosition(iCursor.iY);
+			UpdateCursor(TPoint(pos, iCursor.iY));
+			break;
+			}
+		case EKeyBackspace:
+			DeleteTextL(-1);
+			break;
+		case EKeyDelete:
+			DeleteTextL(1);
+			break;
+		case CTRL('s'):
+			Save();
+			break;
+		case CTRL('a'):
+			SaveAs();
+			break;
+		case CTRL('k'):
+			DeleteCurrentLineL();
+			break;
+		case CTRL('g'):
+			GoToLine();
+			break;
+		case CTRL('f'):
+			Find();
+			break;
+		case CTRL('v'):
+			PasteL();
+			break;
+		case CTRL('c'):
+			if (MarkActive()) CopyOrCutL(EFalse);
+			else SetMark();
+			break;
+		case CTRL('x'):
+			if (MarkActive()) CopyOrCutL(ETrue);
+			else SetMark();
+			break;
+		default:
+			handled = EFalse;
+			break;
+		}
+
+	if (!handled && (aCode == EKeyEnter || aCode == EKeyTab || TChar(aCode).IsPrint()))
+		{
+		TBuf<1> buf;
+		buf.Append(aCode);
+		InsertTextL(buf);
+		handled = ETrue;
+		}
+	return handled;
+}
+
+void CLRTextView::MoveCursor(TInt aX, TInt aY)
+	{
+	if (aX) ASSERT(aY == 0);
+	if (aY) ASSERT(aX == 0); // Can't handle moving in both axes at once
+
+	TInt line = iCursor.iY;
+	TInt dir = (aX > 0) ? 1 : -1;
+	TInt newX = iCursor.iX;
+	while (aX)
+		{
+		newX += dir;
+		if (newX < 0)
+			{
+			// Move to end of previous line
+			newX = iWindow.iWidth; // Validate cursor will fix this, in the case where this causes us to scroll up a line
+			aY--;
+			break; // We don't really support seeking further than one line either way
+			}
+		else if (newX >= iWindow.iWidth-1)
+			{
+			// Move to start of next line
+			newX = 0;
+			aY++;
+			break; // We don't really support seeking further than one line either way
+			}
+
+		if (iLineData->IsScreenPositionValid(line, newX))
+			{
+			aX -= dir;
+			}
+		}
+
+	if (line + aY < 0)
+		{
+		// Scroll up
+		SeekData(iLineData->DocumentPosition(0), aY);
+		}
+	else if (line + aY >= iWindow.iHeight)
+		{
+		// Scroll down
+		SeekData(iLineData->DocumentPosition(0), aY);
+		}
+	else
+		{
+		// Just update cursor
+		if (iLineData->IsScreenPositionValid(line + aY, 0))
+			{
+			// If the line len is -1 it means the doc doesn't extend as far as this line, therefore we shouldn't allow the cursor to be moved to it
+			UpdateCursor(TPoint(newX, iCursor.iY + aY));
+			}
+		}
+	}
+
+void CLRTextView::DoResizeL(const TWindow& /*aOldWindow*/)
+	{
+	DoRedrawL();
+	}
+
+void CLRTextView::DoRedrawL()
+	{
+	iLineData->EnsureCapacityL(iWindow.iWidth, iWindow.iHeight);
+	ValidateCursor();
+	RequestData(ETrue, iLineData->DocumentPosition(0));
+	}
+
+void CLRTextView::DoDrawL()
+	{
+	DoDrawL(iRecursiveUpdateFlag ? *iRecursiveUpdateFlag : ETrue);
+	}
+
+void CLRTextView::DoDrawL(TBool aUpdateCursorAndStatus)
+	{
+	if (iDrawPoint == TPoint(iWindow.iX, iWindow.iY))
+		{
+		iLineData->SetDocumentPositionForLine(0, iRange.iLocation);
+		iLineData->SetFileLineNumberForLine(0, iRangeStartLineNumber);
+		}
+
+	ASSERT(iRange.iLength == iDes.Length());
+	HideCursor(); // Don't show the cursor while we're moving it around drawing - will be shown again by UpdateCursor
+	for (iCurrentDrawDocIndex = 0; iCurrentDrawDocIndex < iDes.Length(); iCurrentDrawDocIndex++)
+		{
+		TUint16 c = iDes[iCurrentDrawDocIndex];
+		if(iDrawPoint.iY >= iWindow.NextY())
+			{
+			break;
+			}
+		if(c <= KLastCtrlChar)
+			HandleControlChar();
+		else
+			AppendChar(c);
+		}
+
+	TBool moreData = !iBuffer.DocumentPositionIsEof(iRange.Max());
+	TBool filledScreen = iDrawPoint.iY >= iWindow.NextY();
+	if (!filledScreen)
+		{
+		WriteChars();
+		LineFinished();
+		}
+	if (!moreData)
+		{
+		iDrawPoint.iY++; // Start from the line *after* the line we were on
+		// In the case where we've finished writing but haven't filled the screen, we need to blank the rest of it (because to avoid flicker we don't do ClearScreens)
+		while (iDrawPoint.iY < iWindow.NextY())
+			{
+			iDrawPoint.iX = iWindow.iX;
+			iLine.Copy(_L("^"));
+			WriteChars();
+			TInt lineNum = iDrawPoint.iY - iWindow.iY;
+			iLineData->SetDocumentPositionForLine(lineNum+1, iLineData->DocumentPosition(lineNum));
+			iLineData->SetFileLineNumberForLine(lineNum, -1);
+			iLineData->LineFinishedAt(lineNum, -1);
+			iDrawPoint.iY++;
+			}
+		}
+
+	if (filledScreen || !moreData)
+		{
+		// Finished - null iDes and restore cursor
+		iDes.Set(NULL, 0);
+		if (aUpdateCursorAndStatus)
+			{
+			UpdateCursor(iCursor);
+			}
+		}
+	else
+		{
+		TBool flagSet = EFalse;
+		if (!iRecursiveUpdateFlag)
+			{
+			iRecursiveUpdateFlag = &aUpdateCursorAndStatus;
+			flagSet = ETrue;
+			}
+		// More data needed to fill the screen
+		RequestData(EFalse, iRange.Max());
+		if (flagSet) iRecursiveUpdateFlag = NULL;
+		}
+}
+
+void CLRTextView::ValidateCursor()
+	{	
+	CTextView::ValidateCursor();
+	TInt maxX = iWindow.iWidth - 1;
+	TInt line = iCursor.iY;
+	while (iCursor.iX > 0 && !iLineData->IsScreenPositionValid(line, iCursor.iX))
+		{
+		// In case the cursor ended up in the dead space in the middle of a tab character
+		iCursor.iX--;
+		}
+	while (line > 0 && !iLineData->IsScreenPositionValid(line, 0))
+		{
+		// In case the cursor has ended up in dead space
+		iCursor.iY--;
+		line--;
+		}
+	maxX = iLineData->LastValidPosition(line);
+	if(iCursor.iX > maxX)
+		iCursor.iX = maxX;
+	}
+
+void CLRTextView::HandleControlChar()
+	{
+	TUint8 c = iDes[iCurrentDrawDocIndex];
+	if (c == KCtrlHTab)
+		{
+		TInt pos = iDrawPoint.iX - iWindow.iX + iLine.Length();
+		TInt numToWrite = TabWidth() - (pos % TabWidth()); // Tabs align to 4-column boundaries rather than just outputting 4 spaces
+		numToWrite = Min(numToWrite, iWindow.iWidth-1-pos); // In case the tab causes a line continuation, we want to make sure the tab ends at the end of the line so the following character will start at column zero
+		for(TInt i=0; i<numToWrite; i++)
+			{
+			TInt line = iDrawPoint.iY - iWindow.iY;
+			TInt col = iDrawPoint.iX - iWindow.iX + iLine.Length();
+			AppendChar(' ');
+			if (i > 0) iLineData->SetPositionIsValid(line, col, EFalse); // i==0 (ie the first screen position of the tab) is a valid place to move the cursor to
+			}
+		}
+	else if (c == '\r')
+		{
+		if (iCurrentDrawDocIndex+1 < iDes.Length() && iDes[iCurrentDrawDocIndex+1] == '\n') iCurrentDrawDocIndex++; // Skip over the CR to the LF
+		WriteLine();
+		}
+	else if (c == '\n')
+		{
+		WriteLine();
+		}
+	else
+		{
+		// unknown control char - so escape it
+		AppendChar('.');
+		}
+	}
+
+void CLRTextView::AppendChar(TUint16 aChar)
+	{
+	iLine.Append(TChar(aChar));
+	iLineData->SetPositionIsValid(iDrawPoint.iY - iWindow.iY, iDrawPoint.iX - iWindow.iX + iLine.Length(), ETrue);
+	if(iDrawPoint.iX + iLine.Length() == iWindow.NextX()-1)
+		{
+		WriteChars();
+		WriteLineContinue();
+		GoToNextLine();
+		}
+	}
+
+//Writes the current line on the screen and moves to the next verse and line
+void CLRTextView::WriteLine()
+	{
+	TInt thisLine = iDrawPoint.iY-iWindow.iY;
+	WriteChars();
+	GoToNextLine();
+	iLineData->SetFileLineNumberForLine(thisLine+1, iLineData->FileLineNumber(thisLine) + 1);
+	if (iOldNextLine != -1 && thisLine+1 == iOldNextLine && iLineData->DocumentPosition(iOldNextLine) == iOldNextLineDocPos + iPredictedOldNextLineDocPosDelta)
+		{
+		// We have drawn everything we need to, and the drawing didn't cause the former next line to move
+		iDrawPoint.iY = iWindow.NextY();
+		UpdateDocumentPositionsStartingAtLine(iOldNextLine+1, iPredictedOldNextLineDocPosDelta);
+		}
+	iOldNextLine = -1;
+	}
+
+//Writes the current line on the screen
+void CLRTextView::WriteChars()
+	{
+	/* As an performance optimisation, we avoid calling ClearScreen when redrawing. Therefore we must ensure we 
+	 * write to the entire line to prevent artifacts. 
+	 *
+	 * There are 2 approaches to this - either using CConsoleBase::ClearToEndOfLine or padding the line with 
+	 * spaces up to the window width. ClearToEndOfLine is really slow in WINSCW tshell which is why it's not
+	 * used by default. On target it's probably more efficient to use it.
+	 */
+	TPoint drawPoint(iDrawPoint);
+	TPoint endPoint(drawPoint.iX + iLine.Length(), drawPoint.iY);
+	iConsole.SetCursorPosAbs(drawPoint);
+#ifdef FSHELL_WSERV_SUPPORT
+// text windowserver is really slow at ClearToEndOfLine
+#define USE_CLEARTOENDOFLINE
+#endif
+#ifdef USE_CLEARTOENDOFLINE
+	iConsole.Write(iLine);
+	iConsole.ClearToEndOfLine();
+#else
+	iLine.AppendFill(' ', iWindow.iWidth - (iDrawPoint.iX-iWindow.iX) - iLine.Length());
+	iConsole.Write(iLine);
+#endif
+	iLine.Zero();
+	// Remember to reset the console pos and the drawpoint to the end of the line proper in case the WriteChars 
+	// was to half a line and the rest of the line is in another block.
+	iDrawPoint = endPoint;
+	iConsole.SetCursorPosAbs(iDrawPoint);
+	}
+
+// Moves to the next screen line but doesn't move to the next file line (used when a line is longer than the width of the window)
+void CLRTextView::GoToNextLine()
+	{
+	LineFinished();
+	iDrawPoint.iX = iWindow.iX;
+	iDrawPoint.iY++;
+	iLineData->SetPositionIsValid(iDrawPoint.iY-iWindow.iY, 0, ETrue);
+	}
+
+void CLRTextView::LineFinished()
+	{
+	TInt currentLine = iDrawPoint.iY - iWindow.iY;
+	if (currentLine == iWindow.iHeight) return; // This can happen (and is ok) when drawing the last line of the screen
+	iLineData->LineFinishedAt(currentLine, iDrawPoint.iX - iWindow.iX);
+	iLineData->SetDocumentPositionForLine(currentLine+1, iRange.iLocation+iCurrentDrawDocIndex+1); // Plus one cos we haven't drawn the first character of the next line yet
+	}
+
+void CLRTextView::WriteLineContinue()
+	{
+	CColorConsoleBase* cons = iConsoleProvider.ColorConsole();
+	if (cons) cons->SetTextAttribute(ETextAttributeHighlight);
+	_LIT(KContinuation, "\\");
+	iConsole.Write(KContinuation);
+	if (cons) cons->SetTextAttribute(ETextAttributeNormal);
+
+	TInt thisLine = iDrawPoint.iY-iWindow.iY;
+	iLineData->SetFileLineNumberForLine(thisLine+1, iLineData->FileLineNumber(thisLine)); // The new line has the same file number as the current line, because it's a continuation line
+	}
+
+TInt CLRTextView::DocumentPosition() const
+	{
+	TInt line = iCursor.iY;
+	TInt pos = iLineData->DocumentPosition(line);
+	for (TInt i = 0; i < iCursor.iX; i++)
+		{
+		if (iLineData->IsScreenPositionValid(line, i)) pos++;
+		}
+	return pos;
+	}
+
+void CLRTextView::InsertTextL(const TDesC& aText)
+	{
+	if (!iBuffer.Editable() || iLineIsErrorMessage) return;
+	
+	CancelMark();
+	TInt pos = DocumentPosition();
+	iBuffer.InsertTextL(pos, aText);
+	if (iCursor.iX == iWindow.iWidth-1)
+		{
+		// If the cursor is over a line continuation character (which it will be if it gets to this value) move cursor to the real insertion point at the start of the next line before doing anything - makes the logic much easier!
+		MoveCursor(1, 0);
+		}
+
+	// If we can be sure it won't cause a reflow, shortcut the requesting and redrawing mechanism, for performance reasons
+	TPoint cursorPos = WindowLocationForDocumentPosition(pos);
+	TInt lineNum = cursorPos.iY;
+	TBool posWasAtEndOfLine = cursorPos.iX == iLineData->LastValidPosition(lineNum) && cursorPos.iX + aText.Length() < iWindow.iWidth-1;
+	if (posWasAtEndOfLine && aText.Locate('\t') == KErrNotFound && aText.Locate('\r') == KErrNotFound)
+		{
+		// Can't optimise if the text contains tabs or newlines, we need to do the full algorithm in that case
+		iConsole.Write(aText);
+		for (TInt i = 0; i < aText.Length(); i++)
+			{
+			iLineData->SetPositionIsValid(lineNum, cursorPos.iX + 1 + i, ETrue); // Each added char makes the character position one after that character valid
+			}
+		for (TInt l = lineNum+1; l <= iWindow.iHeight; l++)
+			{
+			iLineData->SetDocumentPositionForLine(l, iLineData->DocumentPosition(l) + aText.Length());
+			}
+		MoveCursor(aText.Length(),0);
+		}
+	else
+		{
+		RedrawFromPositionToEndOfLine(pos, aText.Length());
+		CenterDocPosOnScreen(pos + aText.Length());
+		}
+	}
+
+void CLRTextView::RedrawFromPositionToEndOfLine(TInt aDocumentPosition, TInt aNumCharsInserted)
+	{
+	TPoint point = WindowLocationForDocumentPosition(aDocumentPosition);
+	ASSERT(point.iY >= 0);
+	iDrawPoint = point + iWindow.Origin();
+	// Try and avoid redrawing the whole screen if the RequestData doesn't make the line longer (in terms of screen lines)
+	TInt lastScreenLineForCurrentLine = point.iY;
+	while (iLineData->FileLineNumber(lastScreenLineForCurrentLine+1) == iLineData->FileLineNumber(lastScreenLineForCurrentLine))
+		{
+		// We have to allow for the current line wrapping onto additional screen lines
+		lastScreenLineForCurrentLine++;
+		}
+	iOldNextLine = lastScreenLineForCurrentLine + 1;
+	iOldNextLineDocPos = iLineData->DocumentPosition(iOldNextLine);
+	iPredictedOldNextLineDocPosDelta = aNumCharsInserted;
+	RequestData(EFalse, aDocumentPosition);
+	}
+
+void CLRTextView::DeleteTextL(TInt aNumChars)
+	{
+	if (!iBuffer.Editable() || iLineIsErrorMessage) return;
+
+	CancelMark();
+	ASSERT(aNumChars >= -1); // Can't backspace more than one character
+	TInt pos = DocumentPosition();
+	TRange range;
+	if (aNumChars < 0)
+		range = TRange(pos + aNumChars, -aNumChars);
+	else
+		range = TRange(pos, aNumChars);
+	range.Intersect(TRange(0, KMaxTInt)); // Clamp the range to zero
+
+	if (range.iLength)
+		{
+		iBuffer.DeleteTextL(range);
+		TInt line = iCursor.iY;
+		if (aNumChars == -1) MoveCursor(-1, 0);
+		// Seems to be an issue using RedrawFromPositionToEndOfLine when deleting from the start of a line - since we know it wouldn't be worth using it in that case, don't do it.
+		if (iCursor.iY == line && aNumChars == -1)
+			{
+			RedrawFromPositionToEndOfLine(range.iLocation, -range.iLength);
+			}
+		else
+			{
+			iDrawPoint = iCursor + iWindow.Origin();
+			RequestData(EFalse, DocumentPosition());
+			}
+		}
+	}
+
+void CLRTextView::DeleteCurrentLineL()
+	{
+	if (!iBuffer.Editable() || iLineIsErrorMessage) return;
+
+	CancelMark();
+	TInt currentLine = iCursor.iY;
+	TInt lineStart = iLineData->DocumentPosition(currentLine);
+	TInt lineLen = iLineData->DocumentPosition(currentLine+1) - lineStart;
+	if (lineLen > 0)
+		{
+		TRange range(lineStart, lineLen);
+		iBuffer.DeleteTextL(range);
+		iDrawPoint = TPoint(iWindow.iX, iWindow.iY + iCursor.iY);
+		RequestData(EFalse, lineStart);
+		}
+	}
+
+void CLRTextView::UpdateCursor(const TPoint& aNewPos)
+	{
+	CTextView::UpdateCursor(aNewPos);
+	UpdateStatus();
+	}
+
+void CLRTextView::UpdateStatus()
+	{
+	if (iLineIsErrorMessage)
+		{
+		iConsoleProvider.WriteStatus(iLine, KNullDesC);
+		return;
+		}
+
+	_LIT(KUnknownDelim, "CRLF"); // Since that's what it will get output as
+	_LIT(KUnknownEnc, "UTF-8"); // ditto
+	const TDesC* lineEnding = &KUnknownDelim;
+#define CASE_LIT(res, x, y) case x: { _LIT(KName, y); res = &KName; break; }
+	switch(iBuffer.DelimiterType())
+		{
+		CASE_LIT(lineEnding, EDelimLF, "LF")
+		CASE_LIT(lineEnding, EDelimCR, "CR")
+		CASE_LIT(lineEnding, EDelimCRLF, "CRLF")
+		CASE_LIT(lineEnding, EDelimUnicode, "UniLF")
+		default:
+			break;
+		}
+	const TDesC* encoding = &KUnknownEnc;
+	switch (iBuffer.Encoding())
+		{
+		CASE_LIT(encoding, EEncodingNarrow, "8-bit")
+		CASE_LIT(encoding, EEncodingUtf8, "UTF-8")
+		CASE_LIT(encoding, EEncodingUtf16LE, "UTF-16LE")
+		CASE_LIT(encoding, EEncodingUtf16BE, "UTF-16BE")
+		default:
+			break;
+		}
+
+	const TDesC* modified = &KNullDesC;
+	if (iBuffer.Modified())
+		{
+		_LIT(KMod, "(*)");
+		modified = &KMod;
+		}
+
+	TInt currentLine = iLineData->FileLineNumber(iCursor.iY) + 1; // Human-readable line numbers are always shown as 1-based
+	TInt currentCol = iCursor.iX + 1; // Human-readable col numbers are always shown as 1-based, annoying as it is we will do the same
+	TInt docPos = DocumentPosition();
+	_LIT(KStatus, "%S %S %S Ln:%d Col:%d Char:%d");
+	_LIT(KShortStatus, "%S %S %S Ln:%d");
+	_LIT(KMarkStatus, "Mark@%d. ^X/^C to complete. Ln:%d Ch:%d");
+	const TDesC* title = &iBuffer.Title();
+	_LIT(KUntitled, "untitled");
+	if (title->Length() == 0) title = &KUntitled;
+	if (MarkActive())
+		{
+		iConsoleProvider.WriteStatus(KNullDesC, KMarkStatus, iMarkDocPos, currentLine, docPos);
+		}
+	else if (iConsole.ScreenSize().iWidth < 50)
+		{
+		iConsoleProvider.WriteStatus(*title, KShortStatus, modified, lineEnding, encoding, currentLine);
+		}
+	else
+		{
+		iConsoleProvider.WriteStatus(*title, KStatus, modified, lineEnding, encoding, currentLine, currentCol, docPos);
+		}
+	}
+
+void CLRTextView::GoToLine()
+	{
+	TBuf<32> lineText;
+	TBool go = iConsoleProvider.QueryText(_L("Go to line: "), lineText);
+	if (go == EFalse) return;
+
+	TLex lex(lineText);
+	TBool gotoLine = ETrue;
+	if (lex.Peek() == 'c')
+		{
+		// Go to character pos
+		lex.Inc();
+		gotoLine = EFalse;
+		}
+
+	TInt pos;
+	TInt err = lex.Val(pos);
+	if (err || pos <= 0)
+		{
+		iConsoleProvider.InfoPrint(_L("Couldn't parse line number '%S'"), &lineText);
+		return;
+		}
+
+	if (gotoLine && pos > 1)
+		{
+		GoToLine(pos-1); // -1 to change 1-based display number to internal 0-based.
+		}
+	else
+		{
+		CenterDocPosOnScreen(pos-1);
+		}
+	}
+
+void CLRTextView::GoToLine(TInt aLine)
+	{
+	TInt err = iBuffer.SeekFromOffset(*this, 0, aLine, KMaxTInt); 
+	if (err)
+		{
+		HandleDataLoadError(err);
+		}
+	else
+		{
+		CenterDocPosOnScreen(iRange.iLocation);
+		}
+	}
+
+void CLRTextView::Find()
+	{
+	TBool searchBackwards = EFalse;
+	TBool proceed = iConsoleProvider.QueryText(_L("Find text: "), iFindText);
+	while (proceed)
+		{
+		TInt nextLineStart = iLineData->DocumentPosition(iCursor.iY + 1);
+		iConsoleProvider.InfoPrint(_L("Searching..."));
+		TInt found = iBuffer.Find(nextLineStart, iFindText, searchBackwards);
+		if (found == KErrNotFound)
+			{
+			iConsoleProvider.InfoPrint(_L("Cannot find '%S'"), &iFindText);
+			proceed = EFalse;
+			}
+		else if (found < 0)
+			{
+			iConsoleProvider.InfoPrint(_L("Error during searching: %d"), found);
+			proceed = EFalse;
+			}
+		else
+			{
+			CenterDocPosOnScreen(found);
+			/*TKeyCode ch = iConsoleProvider.Query(_L("Search again? (next/previous/cancel) "));
+			if (ch == 'n')
+				{
+				searchBackwards = EFalse;
+				continue;
+				}
+			else if (ch == 'p')
+				{
+				searchBackwards = ETrue;
+				continue;
+				}
+			else*/
+				{
+				proceed = EFalse;
+				}
+			}
+		}
+	}
+
+void CLRTextView::CenterDocPosOnScreen(TInt aDocumentPosition)
+	{
+	const TInt numScreenLines = iWindow.iHeight;
+	TPoint point = WindowLocationForDocumentPosition(aDocumentPosition);
+	if (point.iY != -1)
+		{
+		// pos is on screen already - just move the cursor
+		UpdateCursor(point);
+		return;
+		}
+
+	TInt err = iBuffer.SeekFromOffset(*this, aDocumentPosition, -(numScreenLines/2), iWindow.iWidth-1);
+	if (err)
+		{
+		HandleDataLoadError(err);
+		}
+	else
+		{
+		iDrawPoint.SetXY(iWindow.iX, iWindow.iY);
+		DoDrawL(EFalse); // EFalse to say we will update the cursor and status ourselves
+		UpdateCursor(WindowLocationForDocumentPosition(aDocumentPosition));
+		}
+	}
+
+void CLineData::EnsureCapacityL(TInt aWidth, TInt aHeight)
+	{
+	if (!iDocPosAndLine || (TUint)User::AllocLen(iDocPosAndLine) < sizeof(TInt)*2 * (aHeight+1))
+		{
+		User::Free(iDocPosAndLine);
+		iDocPosAndLine = NULL;
+		iDocPosAndLine = (TInt*)User::AllocZL(sizeof(TInt)*2*(aHeight+1));
+		}
+	iLineLen = aWidth-1;
+	TInt lineWidthBytes = ((iLineLen+7)&~7)/8;
+	TInt screenBytes = lineWidthBytes * (aHeight+1);
+	if (!iScreenIndexes || User::AllocLen(iScreenIndexes) < screenBytes)
+		{
+		User::Free(iScreenIndexes);
+		iScreenIndexes = NULL;
+		iScreenIndexes = (TUint8*)User::AllocZL(screenBytes);
+		}
+	SetPositionIsValid(0, 0, ETrue); // This position is always valid even if the document is empty
+	}
+
+TInt CLineData::DocumentPosition(TInt aLine) const
+	{
+	return iDocPosAndLine[aLine*2];
+	}
+
+TInt CLineData::FileLineNumber(TInt aLine) const
+	{
+	return iDocPosAndLine[aLine*2 + 1];
+	}
+
+const TUint8& CLineData::ByteForPos(TInt aLine, TInt aCol, TInt& aOffset) const
+	{
+	return const_cast<CLineData*>(this)->ByteForPos(aLine, aCol, aOffset);
+	}
+
+TUint8& CLineData::ByteForPos(TInt aLine, TInt aCol, TInt& aOffset)
+	{
+	TInt lineWidthBytes = ((iLineLen+7)&~7)/8;
+	TInt byteIndex = aLine*lineWidthBytes + aCol/8;
+	aOffset = aCol%8;
+	TUint8& b = iScreenIndexes[byteIndex];
+	return b;
+	}
+
+TBool CLineData::IsScreenPositionValid(TInt aLine, TInt aCol) const
+	{
+	TInt offset;
+	TUint8 b = ByteForPos(aLine, aCol, offset);
+	return b & (1 << offset);
+	}
+
+void CLineData::SetDocumentPositionForLine(TInt aLine, TInt aDocumentPosition)
+	{
+	ASSERT(iDocPosAndLine && aLine*2*sizeof(TInt) < (TUint)User::AllocLen(iDocPosAndLine));
+	iDocPosAndLine[aLine*2] = aDocumentPosition;
+	}
+
+void CLineData::SetFileLineNumberForLine(TInt aLine, TInt aFileLineNumber)
+	{
+	ASSERT(iDocPosAndLine && (aLine*2+1)*sizeof(TInt) < (TUint)User::AllocLen(iDocPosAndLine));
+	iDocPosAndLine[aLine*2 + 1] = aFileLineNumber;
+	}
+
+void CLineData::SetPositionIsValid(TInt aLine, TInt aCol, TBool aValid)
+	{
+	TInt offset;
+	TUint8& b = ByteForPos(aLine, aCol, offset);
+	if (aValid)
+		{
+		b |= (1<<offset);
+		}
+	else
+		{
+		b &= ~(1<<offset);
+		}
+	}
+
+void CLineData::LineFinishedAt(TInt aLine, TInt aCol)
+	{
+	if (aCol >=0 && aCol < iLineLen) SetPositionIsValid(aLine, aCol, ETrue); //  The last position on the line is valid even though nothing is drawn there
+	TInt offset;
+	TUint8& b = ByteForPos(aLine, aCol+1, offset); // Plus one as we want to clear everything beyond the aCol position
+	// Clear everything above the offset'th bit
+	b &= (1 << offset) - 1;
+	// And clear all bytes from b to the end of the line
+	TUint8* endb = &ByteForPos(aLine, iLineLen-1, offset);
+	// Armv5 doesn't like passing in a length of zero to memset, not sure why. Anyway, check there are actually some other bytes that need clearing
+	if (endb > &b)
+		{
+		Mem::FillZ((&b)+1, endb - &b);
+		}
+	}
+
+CLineData::~CLineData()
+	{
+	User::Free(iDocPosAndLine);
+	User::Free(iScreenIndexes);
+	}
+
+TInt CLineData::LastValidPosition(TInt aLine) const
+	{
+	TInt offset;
+	TInt pos = iLineLen-1; // Start comparing from the last possible valid position
+	const TUint8* b = &ByteForPos(aLine, pos, offset);
+	while(*b == 0)
+		{
+		b--; // Optimisation to skip over blanks
+		pos -= offset+1;
+		offset = 7;
+		}
+
+	while (offset >= 0 && ((*b) & (1<<offset)) == 0)
+		{
+		pos--;
+		offset--;
+		}
+	ASSERT(IsScreenPositionValid(aLine, pos)); // Something's gone very wrong in the logic of this function if this assert is not true!
+	return pos;
+	}
+
+void CLRTextView::CopyToClipboardL(const TDesC& aBuf)
+	{
+	LtkUtils::CopyToClipboardL(aBuf, &iConsoleProvider.Fs());
+	}
+
+HBufC* CLRTextView::GetFromClipboardL()
+	{
+	return LtkUtils::GetFromClipboardL(&iConsoleProvider.Fs());
+	}
+
+void CLRTextView::PasteL()
+	{
+	HBufC* text = GetFromClipboardL();
+	CleanupStack::PushL(text);
+	if (text->Length() == 0)
+		{
+		iConsoleProvider.InfoPrint(_L("Nothing to paste"));
+		}
+	else
+		{
+		InsertTextL(*text);
+		}
+	CleanupStack::PopAndDestroy(text);
+	}
+
+TPoint CLRTextView::WindowLocationForDocumentPosition(TInt aDocumentPosition) const
+	{
+	TPoint result(-1,-1); // We return this if doc pos is not on screen
+	const TInt numScreenLines = iWindow.iHeight;
+	if (aDocumentPosition >= iLineData->DocumentPosition(0) && aDocumentPosition < iLineData->DocumentPosition(numScreenLines))
+		{
+		// pos is on screen
+		for (TInt line = 0; line < numScreenLines; line++)
+			{
+			if (aDocumentPosition < iLineData->DocumentPosition(line+1))
+				{
+				result.iY = line;
+				TInt docPos = iLineData->DocumentPosition(line);
+				TInt column = 0;
+				// Find the column screen position whose document position is what we want
+				for (; docPos <= aDocumentPosition; column++)
+					{
+					if (iLineData->IsScreenPositionValid(line, column))
+						{
+						docPos++;
+						}
+					}
+				result.iX = column-1;
+				break;
+				}
+			}
+		}
+	return result;
+	}
+
+void CLRTextView::SetMark()
+	{
+	iMarkFlasher->Cancel();
+	iMarkDocPos = DocumentPosition();
+	iMarkShown = EFalse;
+	const TInt KFlashInterval = 500000;
+	iMarkFlasher->Start(0, KFlashInterval, TCallBack(&FlashMark, this));
+	UpdateStatus();
+	iConsoleProvider.InfoPrint(_L("Mark set. Move cursor then press ^X/^C to cut/copy"));
+	}
+
+void CLRTextView::CancelMark()
+	{
+	if (MarkActive())
+		{
+		iMarkFlasher->Cancel();
+		if (WindowLocationForDocumentPosition(iMarkDocPos).iY != -1)
+			{
+			// If the mark pos is on screen, redraw it to get rid of the '*'
+			RedrawFromPositionToEndOfLine(iMarkDocPos, 0);
+			}
+		}
+	}
+
+TInt CLRTextView::FlashMark(TAny* aSelf)
+	{
+	static_cast<CLRTextView*>(aSelf)->DoFlashMark();
+	return 1;
+	}
+
+void CLRTextView::DoFlashMark()
+	{
+	TPoint markPos = WindowLocationForDocumentPosition(iMarkDocPos);
+	if (markPos.iY != -1)
+		{
+		TPoint cursor = iConsole.CursorPos();
+		iConsole.SetCursorPosAbs(markPos);
+		if (iMarkShown)
+			iConsole.Write(_L(" "));
+		else
+			iConsole.Write(_L("*"));
+		iMarkShown = !iMarkShown;
+		iConsole.SetCursorPosAbs(cursor);
+		}
+	}
+
+TBool CLRTextView::MarkActive() const
+	{
+	return iMarkFlasher->IsActive();
+	}
+
+void CLRTextView::CopyOrCutL(TBool aCut)
+	{
+	TInt docPos = DocumentPosition();
+	const TInt start = Min(docPos, iMarkDocPos);
+	const TInt len = Abs(docPos - iMarkDocPos);
+	CancelMark();
+	RBuf textCopy;
+	CleanupClosePushL(textCopy);
+	TInt dataPos = start;
+	while(ETrue)
+		{
+		TInt err = iBuffer.GetData(*this, dataPos);
+		if (err)
+			{
+			HandleDataLoadError(err);
+			CleanupStack::PopAndDestroy(&textCopy);
+			return;
+			}
+		else
+			{
+			TPtrC text = iDes.Left(len - textCopy.Length());
+			if (textCopy.MaxLength() == 0 && len <= iRange.iLength)
+				{
+				// All fits in one buffer, no need to copy anything!
+				CopyToClipboardL(text);
+				break;
+				}
+			else
+				{
+				// Need to do some copying
+				if (textCopy.MaxLength() == 0) textCopy.CreateL(len);
+				textCopy.Append(text);
+				dataPos += text.Length();
+				if (textCopy.Length() == len)
+					{
+					// Done
+					CopyToClipboardL(textCopy);
+					break;
+					}
+				}
+			}
+		}
+	CleanupStack::PopAndDestroy(&textCopy);
+	if (aCut)
+		{
+		TRange range(start, len);
+		iBuffer.DeleteTextL(range);
+		iCursor = WindowLocationForDocumentPosition(start);
+		if (iCursor.iY == -1)
+			{
+			// Start of the selection is offscreen - force a scroll
+			CenterDocPosOnScreen(start);
+			}
+		else
+			{
+			iDrawPoint = iCursor + iWindow.Origin();
+			RequestData(EFalse, start);
+			}
+		iConsoleProvider.InfoPrint(_L("Text cut to clipboard"));
+		}
+	else
+		{
+		iConsoleProvider.InfoPrint(_L("Text copied to clipboard"));
+		}
+	}
+
+void CLRTextView::UpdateDocumentPositionsStartingAtLine(TInt aLine, TInt aDelta)
+	{
+	// This is important because even though we can optimise away the drawing, when a line has changed
+	// length but not caused wrapping, we still need to make sure the document positions of all the
+	// lines we didn't draw are correct to reflect the extra characters.
+	for (TInt i = aLine; i < iWindow.iHeight+1; i++)
+		{
+		iLineData->SetDocumentPositionForLine(i, iLineData->DocumentPosition(i) + aDelta);
+		}
+	}