commands/fed/src/lrtextview.cpp
author Tom Sutcliffe <thomas.sutcliffe@accenture.com>
Thu, 26 Aug 2010 00:49:35 +0100
changeset 45 534b01198c2d
parent 0 7f656887cf89
permissions -rw-r--r--
Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase. Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.

// 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);
		}
	}