From c87666f64c80852bed120b2048486c2fbb4d4eed Mon Sep 17 00:00:00 2001 From: Andreas Martens Date: Thu, 26 Oct 2000 06:41:24 +0000 Subject: New: Two-line-portions with surrounding brackets --- sw/source/core/text/frmform.cxx | 15 ++-- sw/source/core/text/inftxt.hxx | 6 +- sw/source/core/text/itrcrsr.cxx | 9 +- sw/source/core/text/itrform2.cxx | 19 ++-- sw/source/core/text/porexp.cxx | 7 +- sw/source/core/text/porexp.hxx | 14 ++- sw/source/core/text/pormulti.cxx | 185 +++++++++++++++++++++++++++++++++------ sw/source/core/text/pormulti.hxx | 38 +++++++- 8 files changed, 240 insertions(+), 53 deletions(-) diff --git a/sw/source/core/text/frmform.cxx b/sw/source/core/text/frmform.cxx index 04f8056b5aab..2406c7eee8d7 100644 --- a/sw/source/core/text/frmform.cxx +++ b/sw/source/core/text/frmform.cxx @@ -2,9 +2,9 @@ * * $RCSfile: frmform.cxx,v $ * - * $Revision: 1.2 $ + * $Revision: 1.3 $ * - * last change: $Author: ama $ $Date: 2000-10-16 13:11:59 $ + * last change: $Author: ama $ $Date: 2000-10-26 07:36:20 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses @@ -1324,12 +1324,17 @@ void SwTxtFrm::_Format( SwTxtFormatter &rLine, SwTxtFormatInfo &rInf, if( pFld ) { pRest->TakeNextOffset( pFld ); - xub_StrLen nEndOf; + const SwTxtAttr* pTwoLines; // If we get a field portion rest in a multi-line part of the // text, we have to create the surrounding multi-portion, too. - if( GetOfst() && 0 < (nEndOf = rInf.EndOfMulti( GetOfst()-1) ) ) + if( GetOfst() && + 0 != (pTwoLines = rInf.GetTwoLines( GetOfst()-1) ) ) { - SwMultiPortion* pTmp = new SwMultiPortion( nEndOf ); + SwMultiPortion* pTmp = + new SwMultiPortion( *pTwoLines->GetEnd() ); +#ifdef DEBUG + pTmp->SetBrackets( 0, ']' ); +#endif pTmp->SetFldRest( pRest ); rInf.SetRest( pTmp ); } diff --git a/sw/source/core/text/inftxt.hxx b/sw/source/core/text/inftxt.hxx index 6e30efba50c7..5068a5c19a8c 100644 --- a/sw/source/core/text/inftxt.hxx +++ b/sw/source/core/text/inftxt.hxx @@ -2,9 +2,9 @@ * * $RCSfile: inftxt.hxx,v $ * - * $Revision: 1.2 $ + * $Revision: 1.3 $ * - * last change: $Author: ama $ $Date: 2000-10-16 13:17:20 $ + * last change: $Author: ama $ $Date: 2000-10-26 07:35:39 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses @@ -212,7 +212,7 @@ public: // EndOfMulti returns the end of the multi-line part of the text, // but zero, if nPos is not inside any multi-line part. - xub_StrLen EndOfMulti( const xub_StrLen nPos ) const; + const SwTxtAttr* GetTwoLines( const xub_StrLen nPos ) const; inline sal_Bool OnWin() const { return bOnWin; } inline void SetOnWin( const sal_Bool bNew ) { bOnWin = bNew; } diff --git a/sw/source/core/text/itrcrsr.cxx b/sw/source/core/text/itrcrsr.cxx index 841a6caac974..ad2b5851e7cc 100644 --- a/sw/source/core/text/itrcrsr.cxx +++ b/sw/source/core/text/itrcrsr.cxx @@ -2,9 +2,9 @@ * * $RCSfile: itrcrsr.cxx,v $ * - * $Revision: 1.5 $ + * $Revision: 1.6 $ * - * last change: $Author: ama $ $Date: 2000-10-23 10:18:00 $ + * last change: $Author: ama $ $Date: 2000-10-26 07:36:55 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses @@ -476,6 +476,9 @@ sal_Bool SwTxtCursor::GetCharRect( SwRect* pOrig, const xub_StrLen nOfst, Next(); bRet = GetCharRect( pOrig, nOfst, pCMS, nMax ); pOrig->Pos().X() += nX; + if( ((SwMultiPortion*)pPor)->HasBrackets() ) + pOrig->Pos().X() += + ((SwMultiPortion*)pPor)->PreWidth(); pCurr = pOldCurr; nStart = nOldStart; return bRet; @@ -893,6 +896,8 @@ xub_StrLen SwTxtCursor::GetCrsrOfst( SwPosition *pPos, const Point &rPoint, // In a multi-portion we use GetCrsrOfst()-funtion rekursively SwTxtCursorSave aSave( (SwTxtCursor*)this, (SwMultiPortion*)pPor, rPoint.Y(), nCurrStart ); + if( ((SwMultiPortion*)pPor)->HasBrackets() ) + nX -= ((SwMultiPortion*)pPor)->PreWidth(); return GetCrsrOfst( pPos, Point( nLeftMargin + nX, rPoint.Y() ), nChgNode, pCMS ); } diff --git a/sw/source/core/text/itrform2.cxx b/sw/source/core/text/itrform2.cxx index 6d46d8573dad..13183ad0118c 100644 --- a/sw/source/core/text/itrform2.cxx +++ b/sw/source/core/text/itrform2.cxx @@ -2,9 +2,9 @@ * * $RCSfile: itrform2.cxx,v $ * - * $Revision: 1.7 $ + * $Revision: 1.8 $ * - * last change: $Author: ama $ $Date: 2000-10-17 14:20:49 $ + * last change: $Author: ama $ $Date: 2000-10-26 07:39:36 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses @@ -68,6 +68,9 @@ #ifndef _COM_SUN_STAR_TEXT_SCRIPTTYPE_HDL_ #include #endif +#ifndef _TXATBASE_HXX //autogen +#include +#endif #ifndef _LAYFRM_HXX #include // GetFrmRstHeight, etc #endif @@ -1239,9 +1242,15 @@ SwLinePortion *SwTxtFormatter::NewPortion( SwTxtFormatInfo &rInf ) if( !pMulti ) { // We open a multiportion part, if we enter a multi-line part // of the paragraph. - xub_StrLen nEndOfMulti = rInf.EndOfMulti( rInf.GetIdx() ); - if( nEndOfMulti ) - return new SwMultiPortion( nEndOfMulti); + const SwTxtAttr* pTwoLines = rInf.GetTwoLines( rInf.GetIdx() ); + if( pTwoLines ) + { + SwMultiPortion* pTmp = new SwMultiPortion(*pTwoLines->GetEnd()); +#ifdef DEBUG + pTmp->SetBrackets( '(', ')' ); +#endif + return pTmp; + } } // 5010: Tabs und Felder xub_Unicode cChar = rInf.GetHookChar(); diff --git a/sw/source/core/text/porexp.cxx b/sw/source/core/text/porexp.cxx index 8757ec08eb4a..17b5e6b649d8 100644 --- a/sw/source/core/text/porexp.cxx +++ b/sw/source/core/text/porexp.cxx @@ -2,9 +2,9 @@ * * $RCSfile: porexp.cxx,v $ * - * $Revision: 1.2 $ + * $Revision: 1.3 $ * - * last change: $Author: ama $ $Date: 2000-10-19 13:51:28 $ + * last change: $Author: ama $ $Date: 2000-10-26 07:41:24 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses @@ -261,7 +261,8 @@ sal_Bool SwBlankPortion::Format( SwTxtFormatInfo &rInf ) void SwBlankPortion::Paint( const SwTxtPaintInfo &rInf ) const { - rInf.DrawViewOpt( *this, POR_BLANK ); + if( !bMulti ) // No gray background for multiportion brackets + rInf.DrawViewOpt( *this, POR_BLANK ); SwExpandPortion::Paint( rInf ); } diff --git a/sw/source/core/text/porexp.hxx b/sw/source/core/text/porexp.hxx index 39b05fbf364d..ee104baffaaa 100644 --- a/sw/source/core/text/porexp.hxx +++ b/sw/source/core/text/porexp.hxx @@ -2,9 +2,9 @@ * * $RCSfile: porexp.hxx,v $ * - * $Revision: 1.1.1.1 $ + * $Revision: 1.2 $ * - * last change: $Author: hr $ $Date: 2000-09-19 00:08:25 $ + * last change: $Author: ama $ $Date: 2000-10-26 07:40:52 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses @@ -91,9 +91,15 @@ public: class SwBlankPortion : public SwExpandPortion { xub_Unicode cChar; + BOOL bMulti; // For multiportion brackets public: - inline SwBlankPortion( xub_Unicode cCh ) - { cChar = cCh; SetLen(1); SetWhichPor( POR_BLANK ); } + inline SwBlankPortion( xub_Unicode cCh, BOOL bMult = sal_False ) + : cChar( cCh ), bMulti( bMult ) + { cChar = cCh; SetLen(1); SetWhichPor( POR_BLANK ); } + + BOOL IsMulti() const { return bMulti; } + void SetMulti( BOOL bNew ) { bMulti = bNew; } + virtual SwLinePortion *Compress(); virtual sal_Bool GetExpTxt( const SwTxtSizeInfo &rInf, XubString &rTxt ) const; virtual void FormatEOL( SwTxtFormatInfo &rInf ); diff --git a/sw/source/core/text/pormulti.cxx b/sw/source/core/text/pormulti.cxx index 997f329f9367..55e4a9720690 100644 --- a/sw/source/core/text/pormulti.cxx +++ b/sw/source/core/text/pormulti.cxx @@ -2,9 +2,9 @@ * * $RCSfile: pormulti.cxx,v $ * - * $Revision: 1.3 $ + * $Revision: 1.4 $ * - * last change: $Author: ama $ $Date: 2000-10-23 10:19:21 $ + * last change: $Author: ama $ $Date: 2000-10-26 07:37:52 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses @@ -99,6 +99,7 @@ SwMultiPortion::~SwMultiPortion() { delete pFldRest; + delete pBracket; } void SwMultiPortion::Paint( const SwTxtPaintInfo &rInf ) const @@ -129,35 +130,130 @@ void SwMultiPortion::CalcSize( SwTxtFormatter& rLine ) } while ( pLay ); } +/*-----------------25.10.00 09:51------------------- + * SwMultiPortion::PaintBracket paints the wished bracket, + * if the multiportion has surrounding brackets. + * The X-position of the SwTxtPaintInfo will be modified: + * the open bracket sets position behind itself, + * the close bracket in front of itself. + * --------------------------------------------------*/ + +void SwMultiPortion::PaintBracket( const SwTxtPaintInfo &rInf, + sal_Bool bOpen ) const +{ + sal_Unicode cCh = bOpen ? pBracket->cPre : pBracket->cPost; + if( !cCh ) + return; + KSHORT nChWidth = bOpen ? PreWidth() : PostWidth(); + if( !nChWidth ) + return; + SwBlankPortion aBlank( cCh, sal_True ); + aBlank.SetAscent( pBracket->nAscent ); + aBlank.Width( nChWidth ); + aBlank.Height( pBracket->nHeight ); + SwFont* pTmpFnt = new SwFont( *rInf.GetFont() ); + pTmpFnt->SetProportion( 100 ); + SwFontSave aSave( rInf, pTmpFnt ); + aBlank.Paint( rInf ); +} + +/*-----------------25.10.00 16:26------------------- + * SwMultiPortion::SetBrackets creates the bracket-structur + * and fills it, if not both characters are 0x00. + * --------------------------------------------------*/ + +void SwMultiPortion::SetBrackets( sal_Unicode cPre, sal_Unicode cPost ) +{ + if( cPre || cPost ) + { + pBracket = new SwBracket; + pBracket->cPre = cPre; + pBracket->cPost = cPost; + } +} + +/*-----------------25.10.00 16:29------------------- + * SwMultiPortion::FormatBrackets + * calculates the size of the brackets => pBracket, + * reduces the nMaxWidth-parameter ( minus bracket-width ) + * and moves the rInf-x-position behind the opening bracket. + * --------------------------------------------------*/ + +void SwMultiPortion::FormatBrackets( SwTxtFormatInfo &rInf, SwTwips& nMaxWidth ) +{ + nMaxWidth -= rInf.X(); + SwFont* pTmpFnt = new SwFont( *rInf.GetFont() ); + pTmpFnt->SetProportion( 100 ); + SwFontSave aSave( rInf, pTmpFnt ); + pBracket->nAscent = rInf.GetAscent(); + if( pBracket->cPre ) + { + String aStr( pBracket->cPre ); + SwPosSize aSize = rInf.GetTxtSize( aStr ); + pBracket->nHeight = aSize.Height(); + if( nMaxWidth > aSize.Width() ) + { + pBracket->nPreWidth = aSize.Width(); + nMaxWidth -= aSize.Width(); + rInf.X( rInf.X() + aSize.Width() ); + } + else + { + pBracket->nPreWidth = 0; + nMaxWidth = 0; + } + } + else + pBracket->nPreWidth = 0; + if( pBracket->cPost ) + { + String aStr( pBracket->cPost ); + SwPosSize aSize = rInf.GetTxtSize( aStr ); + pBracket->nHeight = aSize.Height(); + if( nMaxWidth > aSize.Width() ) + { + pBracket->nPostWidth = aSize.Width(); + nMaxWidth -= aSize.Width(); + } + else + { + pBracket->nPostWidth = 0; + nMaxWidth = 0; + } + } + else + pBracket->nPostWidth = 0; + nMaxWidth += rInf.X(); +} + /*-----------------13.10.00 16:22------------------- - * If we're inside a double-line-attribute, the result - * will be the end of the attribute, + * If we're inside a two-line-attribute, + * the attribute will be returned, * otherwise the function returns zero. * --------------------------------------------------*/ -xub_StrLen SwTxtSizeInfo::EndOfMulti( const xub_StrLen nPos ) const +const SwTxtAttr* SwTxtSizeInfo::GetTwoLines( const xub_StrLen nPos ) const { - xub_StrLen nRet = 0; const SwpHints *pHints = pFrm->GetTxtNode()->GetpSwpHints(); if( !pHints ) - return sal_False; + return NULL; for( MSHORT i = 0; i < pHints->Count(); ++i ) { - const SwTxtAttr *pPos = (*pHints)[i]; - xub_StrLen nStart = *pPos->GetStart(); + const SwTxtAttr *pRet = (*pHints)[i]; + xub_StrLen nStart = *pRet->GetStart(); if( nPos < nStart ) break; + if( RES_TXTATR_TWO_LINES == pRet->Which() #ifdef FOR_YOUR_OWN_RISK - if( RES_CHRATR_UNDERLINE == pPos->Which() ) + || RES_CHRATR_UNDERLINE == pRet->Which() +#endif + ) { - nRet = *pPos->GetEnd(); - if( nPos == nStart || nRet > nPos ) - break; - nRet = 0; + if( nPos == nStart || *pRet->GetEnd() > nPos ) + return pRet; } -#endif } - return nRet; + return NULL; } /*-----------------13.10.00 16:24------------------- @@ -176,6 +272,14 @@ void SwTxtPainter::PaintMultiPortion( const SwRect &rPaint, xub_StrLen nOldIdx = GetInfo().GetIdx(); SvShorts *pOldSpaceAdd = GetInfo().GetpSpaceAdd(); GetInfo().SetSpaceAdd( NULL ); + if( rMulti.HasBrackets() ) + { + SeekAndChg( GetInfo() ); + rMulti.PaintBracket( GetInfo(), sal_True ); + GetInfo().X( GetInfo().X() + rMulti.PreWidth() ); + } + + KSHORT nTmpX = GetInfo().X(); SwLineLayout* pLay = &rMulti.GetRoot();// the first line of the multiportion SwLinePortion* pPor = pLay->GetFirstPortion();//first portion of these line @@ -232,23 +336,30 @@ void SwTxtPainter::PaintMultiPortion( const SwRect &rPaint, pLay = pLay->GetNext(); pPor = pLay->GetFirstPortion(); bRest = pLay->IsRest(); - GetInfo().X( nOldX ); + GetInfo().X( nTmpX ); // We switch to the baseline of the next inner line GetInfo().Y( GetInfo().Y() + rMulti.GetRoot().Height() - rMulti.GetRoot().GetAscent() + pLay->GetAscent() ); } } while( pPor ); - // Restore the saved values - GetInfo().SetLen( nOldLen ); GetInfo().SetIdx( nOldIdx ); - GetInfo().X( nOldX ); GetInfo().Y( nOldY ); + + if( rMulti.HasBrackets() ) + { + SeekAndChg( GetInfo() ); + GetInfo().X( nOldX + rMulti.Width() - rMulti.PostWidth() ); + rMulti.PaintBracket( GetInfo(), sal_False ); + } + // Restore the saved values + GetInfo().X( nOldX ); + GetInfo().SetLen( nOldLen ); GetInfo().SetSpaceAdd( pOldSpaceAdd ); } /*-----------------13.10.00 16:46------------------- - * SwTxtPainter::PaintMultiPortion manages the formatting of a SwMultiPortion. + * SwTxtFormatter::BuildMultiPortion manages the formatting of a SwMultiPortion. * External, for the calling function, it seems to be a normal Format-function, * internal it is like a SwTxtFrm::_Format with multiple BuildPortions * --------------------------------------------------*/ @@ -256,11 +367,17 @@ void SwTxtPainter::PaintMultiPortion( const SwRect &rPaint, BOOL SwTxtFormatter::BuildMultiPortion( SwTxtFormatInfo &rInf, SwMultiPortion& rMulti ) { + SwTwips nMaxWidth = rInf.Width(); + SeekAndChg( rInf ); + if( rMulti.HasBrackets() ) + rMulti.FormatBrackets( rInf, nMaxWidth ); + + SwTwips nTmpX = rInf.X(); + pMulti = &rMulti; SwLineLayout *pOldCurr = pCurr; xub_StrLen nOldStart = GetStart(); - SwTwips nMinWidth = rInf.X() + 1; - SwTwips nMaxWidth = rInf.Width(); + SwTwips nMinWidth = nTmpX + 1; SwTwips nActWidth = nMaxWidth; SwTxtFormatInfo aInf( rInf, rMulti.GetRoot(), nActWidth ); xub_StrLen nStartIdx = aInf.GetIdx(); @@ -273,7 +390,7 @@ BOOL SwTxtFormatter::BuildMultiPortion( SwTxtFormatInfo &rInf, nStart = nStartIdx; bRet = FALSE; FormatReset( aInf ); - aInf.X( rInf.X() ); + aInf.X( nTmpX ); aInf.Width( nActWidth ); if( rMulti.GetFldRest() ) { @@ -291,7 +408,7 @@ BOOL SwTxtFormatter::BuildMultiPortion( SwTxtFormatInfo &rInf, pCurr->SetNext( new SwLineLayout() ); pCurr = pCurr->GetNext(); nStart = aInf.GetIdx(); - aInf.X( rInf.X() ); + aInf.X( nTmpX ); SwTxtFormatInfo aTmp( aInf, *pCurr, nActWidth ); aTmp.SetRest( aInf.GetRest() ); aInf.SetRest( NULL ); @@ -311,8 +428,8 @@ BOOL SwTxtFormatter::BuildMultiPortion( SwTxtFormatInfo &rInf, } else { - if( nActWidth > rInf.X() + rMulti.Width() + 1 ) - nActWidth = rInf.X() + rMulti.Width() + 1; + if( nActWidth > nTmpX + rMulti.Width() + 1) + nActWidth = nTmpX + rMulti.Width() + 1; nMaxWidth = nActWidth; nActWidth = ( 3 * nMaxWidth + nMinWidth + 3 ) / 4; if( nActWidth >= nMaxWidth ) @@ -326,9 +443,23 @@ BOOL SwTxtFormatter::BuildMultiPortion( SwTxtFormatInfo &rInf, nStart = nOldStart; rMulti.SetLen( rMulti.GetRoot().GetLen() + ( rMulti.GetRoot().GetNext() ? rMulti.GetRoot().GetNext()->GetLen() : 0 ) ); + if( rMulti.HasBrackets() ) + rMulti.Width( rMulti.Width() + rMulti.PreWidth() + rMulti.PostWidth() ); if( bRet ) { SwMultiPortion *pTmp = new SwMultiPortion( nMultiLen + rInf.GetIdx() ); + if( rMulti.HasBrackets() ) + { + pTmp->SetBrackets( rMulti ); + // An empty multiportion needs no brackets. + // Notice: GetLen() might be zero, if the multiportion contains + // the second part of a field and the width might be zero, if + // it contains a note only. In this cases the brackets are okay. + // But if the length and the width are both zero, the multiportion + // is really empty. + if( !rMulti.GetLen() && rMulti.Width() == rMulti.BracketWidth() ) + rMulti.ClearBrackets(); + } ASSERT( !pRest || pRest->InFldGrp(), "BuildMultiPortion: Surprising restportion, field exspected" ); pTmp->SetFldRest( (SwFldPortion*) pRest ); diff --git a/sw/source/core/text/pormulti.hxx b/sw/source/core/text/pormulti.hxx index 65a6978fac45..906fdecabc60 100644 --- a/sw/source/core/text/pormulti.hxx +++ b/sw/source/core/text/pormulti.hxx @@ -2,9 +2,9 @@ * * $RCSfile: pormulti.hxx,v $ * - * $Revision: 1.2 $ + * $Revision: 1.3 $ * - * last change: $Author: ama $ $Date: 2000-10-23 10:19:54 $ + * last change: $Author: ama $ $Date: 2000-10-26 07:37:25 $ * * The Contents of this file are made available subject to the terms of * either of the following licenses @@ -63,11 +63,29 @@ #define _PORMULTI_HXX #include "porlay.hxx" +#include "porexp.hxx" class SwTxtFormatInfo; class SwFldPortion; class SwTxtCursor; class SwLineLayout; +class SwBlankPortion; +class SwTxtPaintInfo; + +/*-----------------25.10.00 16:19------------------- + * A two-line-portion (SwMultiPortion) could have surrounding brackets, + * in this case the structur SwBracket will be used. + * --------------------------------------------------*/ + +struct SwBracket +{ + KSHORT nAscent; // Ascent of the brackets + KSHORT nHeight; // Height of them + KSHORT nPreWidth; // Width of the opening bracket + KSHORT nPostWidth; // Width of the closing bracket + sal_Unicode cPre; // Opening character, e.g. '(' + sal_Unicode cPost; // Closing character, e.g. ')' +}; /*-----------------16.10.00 12:45------------------- * The SwMultiPortion is line portion inside a line portion @@ -78,17 +96,29 @@ class SwMultiPortion : public SwLinePortion { SwLineLayout aRoot; // One or more lines SwFldPortion *pFldRest; // a field rest from the previous line + SwBracket* pBracket; // Surrounding brackets + SwTwips nLineDiff; // Difference of the width of the both lines public: - SwMultiPortion( xub_StrLen nEnd ) : pFldRest( NULL ) + SwMultiPortion( xub_StrLen nEnd ) : pFldRest( 0 ), pBracket( 0 ) { SetWhichPor( POR_MULTI ); SetLen( nEnd ); } ~SwMultiPortion(); const SwLineLayout& GetRoot() const { return aRoot; } SwLineLayout& GetRoot() { return aRoot; } - SwFldPortion* GetFldRest() { return pFldRest; } void SetFldRest( SwFldPortion* pNew ) { pFldRest = pNew; } + inline sal_Bool HasBrackets() const { return 0 != pBracket; } + void SetBrackets( sal_Unicode cPre, sal_Unicode cPost ); + inline void SetBrackets( const SwMultiPortion& rMulti ) + { SetBrackets( rMulti.pBracket->cPre, rMulti.pBracket->cPost ); } + void PaintBracket( const SwTxtPaintInfo& rInf, sal_Bool bOpen ) const; + void FormatBrackets( SwTxtFormatInfo &rInf, SwTwips& nMaxWidth ); + inline KSHORT PreWidth() const { return pBracket->nPreWidth; }; + inline KSHORT PostWidth() const { return pBracket->nPostWidth; } + inline void ClearBrackets(){ pBracket->nPreWidth = pBracket->nPostWidth=0; } + inline KSHORT BracketWidth(){ return PreWidth() + PostWidth(); } + virtual void Paint( const SwTxtPaintInfo &rInf ) const; // Summarize the internal lines to calculate the (external) size -- cgit v1.2.3