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