Skip to content

Commit 8802df8

Browse files
committed
Added interactive code folding to the HTML output
Can be controlled via new option HTML_CODE_FOLDING.
1 parent 69342fe commit 8802df8

25 files changed

+426
-8
lines changed

addon/doxyapp/doxyapp.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class XRefDummyCodeGenerator : public OutputCodeExtension
6262
void writeCodeAnchor(const QCString &) override {}
6363
void startCodeFragment(const QCString &) override {}
6464
void endCodeFragment(const QCString &) override {}
65+
void startFold(int,const QCString &,const QCString &) override {}
66+
void endFold() override {}
6567

6668
// here we are presented with the symbols found by the code parser
6769
void linkableSymbol(int l, const char *sym,Definition *symDef,Definition *context)

addon/doxyparse/doxyparse.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class Doxyparse : public OutputCodeExtension
6868
const SourceLinkInfo &) override {}
6969
void startCodeFragment(const QCString &) override {}
7070
void endCodeFragment(const QCString &) override {}
71+
void startFold(int,const QCString &,const QCString &) override {}
72+
void endFold() override {}
7173

7274
void linkableSymbol(int l, const char *sym, Definition *symDef, Definition *context)
7375
{

src/code.l

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ struct codeYY_state
190190
CallContext theCallContext;
191191
SymbolResolver symbolResolver;
192192
TooltipManager tooltipManager;
193+
std::vector<const Definition *> foldStack;
193194
};
194195

195196
static bool isCastKeyword(const char *s);
@@ -2368,6 +2369,48 @@ static void setClassScope(yyscan_t yyscanner,const QCString &name)
23682369
DBG_CTX((stderr,"--->New class scope '%s'\n",qPrint(yyextra->classScope)));
23692370
}
23702371
2372+
static void codeFolding(yyscan_t yyscanner,const Definition *d)
2373+
{
2374+
if (Config_getBool(HTML_CODE_FOLDING))
2375+
{
2376+
struct yyguts_t *yyg = (struct yyguts_t*)yyscanner;
2377+
while (!yyextra->foldStack.empty())
2378+
{
2379+
const Definition *dd = yyextra->foldStack.back();
2380+
if (dd->getEndBodyLine()+1==yyextra->yyLineNr) // +1 to close the section after the end of the body
2381+
{
2382+
yyextra->code->endFold();
2383+
//printf("%d: end codeFolding for %s [%d..%d]\n",yyextra->yyLineNr,qPrint(dd->name()),dd->getStartDefLine(),dd->getEndBodyLine());
2384+
yyextra->foldStack.pop_back();
2385+
}
2386+
else
2387+
{
2388+
break;
2389+
}
2390+
}
2391+
if (d)
2392+
{
2393+
int startLine = d->getStartDefLine();
2394+
int endLine = d->getEndBodyLine();
2395+
if (endLine!=-1 && startLine!=endLine &&
2396+
// since the end of a section is closed after the last line, we need to avoid starting a
2397+
// new section if the previous section ends at the same line, i.e. something like
2398+
// struct X {
2399+
// ...
2400+
// }; struct S { <- start of S and end of X at the same line
2401+
// ...
2402+
// };
2403+
(yyextra->foldStack.empty() || yyextra->foldStack.back()->getEndBodyLine()!=startLine))
2404+
{
2405+
//printf("%d: start codeFolding for %s [%d..%d]\n",yyextra->yyLineNr,qPrint(d->name()),d->getStartDefLine(),d->getEndBodyLine());
2406+
bool needsSemi = d->definitionType()==Definition::TypeClass;
2407+
yyextra->code->startFold(yyextra->yyLineNr,"{",needsSemi ? "};" : "}");
2408+
yyextra->foldStack.push_back(d);
2409+
}
2410+
}
2411+
}
2412+
}
2413+
23712414
/*! start a new line of code, inserting a line number if yyextra->sourceFileDef
23722415
* is TRUE. If a definition starts at the current line, then the line
23732416
* number is linked to the documentation of that definition.
@@ -2403,6 +2446,7 @@ static void startCodeLine(yyscan_t yyscanner)
24032446
lineAnchor.sprintf("l%05d",yyextra->yyLineNr);
24042447
if (yyextra->currentMemberDef)
24052448
{
2449+
codeFolding(yyscanner,yyextra->currentMemberDef);
24062450
yyextra->code->writeLineNumber(yyextra->currentMemberDef->getReference(),
24072451
yyextra->currentMemberDef->getOutputFileBase(),
24082452
yyextra->currentMemberDef->anchor(),
@@ -2411,14 +2455,20 @@ static void startCodeLine(yyscan_t yyscanner)
24112455
}
24122456
else if (d->isLinkableInProject())
24132457
{
2458+
codeFolding(yyscanner,d);
24142459
yyextra->code->writeLineNumber(d->getReference(),
24152460
d->getOutputFileBase(),
24162461
QCString(),yyextra->yyLineNr,!yyextra->includeCodeFragment);
24172462
setCurrentDoc(yyscanner,lineAnchor);
24182463
}
2464+
else
2465+
{
2466+
codeFolding(yyscanner,nullptr);
2467+
}
24192468
}
24202469
else
24212470
{
2471+
codeFolding(yyscanner,nullptr);
24222472
yyextra->code->writeLineNumber(QCString(),QCString(),QCString(),yyextra->yyLineNr,
24232473
!yyextra->includeCodeFragment);
24242474
}
@@ -3965,6 +4015,7 @@ void CCodeParser::parseCode(OutputCodeList &od,const QCString &className,const Q
39654015
yyextra->collectXRefs = collectXRefs;
39664016
yyextra->inFunctionTryBlock = FALSE;
39674017
yyextra->symbolResolver.setFileScope(fd);
4018+
yyextra->foldStack.clear();
39684019

39694020
if (startLine!=-1)
39704021
yyextra->yyLineNr = startLine;
@@ -4030,6 +4081,14 @@ void CCodeParser::parseCode(OutputCodeList &od,const QCString &className,const Q
40304081
{
40314082
endCodeLine(yyscanner);
40324083
}
4084+
if (Config_getBool(HTML_CODE_FOLDING))
4085+
{
4086+
while (!yyextra->foldStack.empty())
4087+
{
4088+
yyextra->code->endFold();
4089+
yyextra->foldStack.pop_back();
4090+
}
4091+
}
40334092
if (yyextra->exampleFileDef)
40344093
{
40354094
// delete the temporary file definition used for this example

src/config.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2257,6 +2257,15 @@ hr.footer {
22572257
If the \c HTML_DYNAMIC_SECTIONS tag is set to \c YES then the generated HTML
22582258
documentation will contain sections that can be hidden and shown after the
22592259
page has loaded.
2260+
]]>
2261+
</docs>
2262+
</option>
2263+
<option type='bool' id='HTML_CODE_FOLDING' defval='1' depends='GENERATE_HTML'>
2264+
<docs>
2265+
<![CDATA[
2266+
If the \c HTML_CODE_FOLDING and \c SOURCE_BROWSER tags are set to \c YES then
2267+
classes and functions can be dynamically folded and expanded in the generated HTML
2268+
source code.
22602269
]]>
22612270
</docs>
22622271
</option>

src/configimpl.l

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,7 @@ void Config::checkAndCorrect(bool quiet, const bool check)
19641964
adjustBoolSetting( depOption, "GENERATE_TREEVIEW", false );
19651965
adjustBoolSetting( depOption, "SEARCHENGINE", false );
19661966
adjustBoolSetting( depOption, "HTML_DYNAMIC_MENUS", false );
1967+
adjustBoolSetting( depOption, "HTML_CODE_FOLDING", false );
19671968
adjustBoolSetting( depOption, "HTML_DYNAMIC_SECTIONS",false );
19681969
adjustStringSetting(depOption, "HTML_FILE_EXTENSION", ".html");
19691970
adjustColorStyleSetting(depOption);

src/devnullgen.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class DevNullCodeGenerator
3838
void writeCodeAnchor(const QCString &) {}
3939
void startCodeFragment(const QCString &) {}
4040
void endCodeFragment(const QCString &) {}
41+
void startFold(int,const QCString &,const QCString &) {}
42+
void endFold() {}
4143
};
4244

4345
#endif // DEVNULLGEN_H

src/docbookgen.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ class DocbookCodeGenerator
6969
const QCString &anchorId,int l, bool writeLineAnchor);
7070
void startCodeFragment(const QCString &style);
7171
void endCodeFragment(const QCString &style);
72+
void startFold(int,const QCString &,const QCString &) {}
73+
void endFold() {}
7274

7375
void setRelativePath(const QCString &path) { m_relPath = path; }
7476
void setSourceFileName(const QCString &sourceFileName) { m_sourceFileName = sourceFileName; }

src/fortrancode.l

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ struct fortrancodeYY_state
172172

173173
bool endComment = false;
174174
TooltipManager tooltipManager;
175+
std::vector<const Definition *> foldStack;
175176

176177
int fixedCommentAfter = 72;
177178
};
@@ -909,6 +910,47 @@ static void addToSearchIndex(yyscan_t /*yyscanner*/,const QCString &text)
909910
}
910911
}
911912

913+
static void codeFolding(yyscan_t yyscanner,const Definition *d)
914+
{
915+
if (Config_getBool(HTML_CODE_FOLDING))
916+
{
917+
struct yyguts_t *yyg = (struct yyguts_t*)yyscanner;
918+
while (!yyextra->foldStack.empty())
919+
{
920+
const Definition *dd = yyextra->foldStack.back();
921+
if (dd->getEndBodyLine()+1==yyextra->yyLineNr) // +1 to close the section after the end of the body
922+
{
923+
yyextra->code->endFold();
924+
//printf("%d: end codeFolding for %s [%d..%d]\n",yyextra->yyLineNr,qPrint(dd->name()),dd->getStartDefLine(),dd->getEndBodyLine());
925+
yyextra->foldStack.pop_back();
926+
}
927+
else
928+
{
929+
break;
930+
}
931+
}
932+
if (d)
933+
{
934+
int startLine = d->getStartDefLine();
935+
int endLine = d->getEndBodyLine();
936+
if (endLine!=-1 && startLine!=endLine &&
937+
// since the end of a section is closed after the last line, we need to avoid starting a
938+
// new section if the previous section ends at the same line, i.e. something like
939+
// struct X {
940+
// ...
941+
// }; struct S { <- start of S and end of X at the same line
942+
// ...
943+
// };
944+
(yyextra->foldStack.empty() || yyextra->foldStack.back()->getEndBodyLine()!=startLine))
945+
{
946+
//printf("%d: start codeFolding for %s [%d..%d]\n",yyextra->yyLineNr,qPrint(d->name()),d->getStartDefLine(),d->getEndBodyLine());
947+
yyextra->code->startFold(yyextra->yyLineNr,"","");
948+
yyextra->foldStack.push_back(d);
949+
}
950+
}
951+
}
952+
}
953+
912954
/*! start a new line of code, inserting a line number if yyextra->sourceFileDef
913955
* is TRUE. If a definition starts at the current line, then the line
914956
* number is linked to the documentation of that definition.
@@ -934,6 +976,7 @@ static void startCodeLine(yyscan_t yyscanner)
934976
lineAnchor.sprintf("l%05d",yyextra->yyLineNr);
935977
if (yyextra->currentMemberDef)
936978
{
979+
codeFolding(yyscanner,yyextra->currentMemberDef);
937980
yyextra->code->writeLineNumber(yyextra->currentMemberDef->getReference(),
938981
yyextra->currentMemberDef->getOutputFileBase(),
939982
yyextra->currentMemberDef->anchor(),yyextra->yyLineNr,
@@ -942,15 +985,21 @@ static void startCodeLine(yyscan_t yyscanner)
942985
}
943986
else if (d->isLinkableInProject())
944987
{
988+
codeFolding(yyscanner,d);
945989
yyextra->code->writeLineNumber(d->getReference(),
946990
d->getOutputFileBase(),
947991
QCString(),yyextra->yyLineNr,
948992
!yyextra->includeCodeFragment);
949993
setCurrentDoc(yyscanner,lineAnchor);
950994
}
995+
else
996+
{
997+
codeFolding(yyscanner,nullptr);
998+
}
951999
}
9521000
else
9531001
{
1002+
codeFolding(yyscanner,nullptr);
9541003
yyextra->code->writeLineNumber(QCString(),QCString(),QCString(),yyextra->yyLineNr,
9551004
!yyextra->includeCodeFragment);
9561005
}
@@ -1493,6 +1542,7 @@ void FortranCodeParser::parseCode(OutputCodeList & codeOutIntf,
14931542
yyextra->exampleBlock = isExampleBlock;
14941543
yyextra->exampleName = exampleName;
14951544
yyextra->sourceFileDef = fileDef;
1545+
yyextra->foldStack.clear();
14961546
if (isExampleBlock && fileDef==0)
14971547
{
14981548
// create a dummy filedef for the example
@@ -1518,6 +1568,14 @@ void FortranCodeParser::parseCode(OutputCodeList & codeOutIntf,
15181568
{
15191569
endCodeLine(yyscanner);
15201570
}
1571+
if (Config_getBool(HTML_CODE_FOLDING))
1572+
{
1573+
while (!yyextra->foldStack.empty())
1574+
{
1575+
yyextra->code->endFold();
1576+
yyextra->foldStack.pop_back();
1577+
}
1578+
}
15211579
if (!fileDef && isExampleBlock && yyextra->sourceFileDef)
15221580
{
15231581
// delete the temporary file definition used for this example

src/htmlgen.cpp

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,21 @@ void HtmlCodeGenerator::endCodeFragment(const QCString &)
10461046
*m_t << "</div><!-- fragment -->";
10471047
}
10481048

1049+
void HtmlCodeGenerator::startFold(int lineNr,const QCString &startMarker,const QCString &endMarker)
1050+
{
1051+
const int maxLineNrStr = 10;
1052+
char lineNumber[maxLineNrStr];
1053+
qsnprintf(lineNumber,maxLineNrStr,"%05d",lineNr);
1054+
*m_t << "<div class=\"foldopen\" id=\"foldopen" << lineNumber <<
1055+
"\" data-start=\"" << startMarker <<
1056+
"\" data-end=\"" << endMarker <<
1057+
"\">\n";
1058+
}
1059+
1060+
void HtmlCodeGenerator::endFold()
1061+
{
1062+
*m_t << "</div>\n";
1063+
}
10491064

10501065
//--------------------------------------------------------------------------
10511066

@@ -1212,10 +1227,10 @@ void HtmlGenerator::init()
12121227
if (f.is_open())
12131228
{
12141229
TextStream t(&f);
1215-
t << mgr.getAsString("dynsections.js");
1230+
t << replaceVariables(mgr.getAsString("dynsections.js"));
12161231
if (Config_getBool(SOURCE_BROWSER) && Config_getBool(SOURCE_TOOLTIPS))
12171232
{
1218-
t << mgr.getAsString("dynsections_tooltips.js");
1233+
t << replaceVariables(mgr.getAsString("dynsections_tooltips.js"));
12191234
}
12201235
}
12211236
}
@@ -1257,6 +1272,10 @@ void HtmlGenerator::writeTabData()
12571272
mgr.copyResource("sync_off.luma",dname);
12581273
mgr.copyResource("nav_g.png",dname);
12591274
Doxygen::indexList->addImageFile("nav_g.png");
1275+
mgr.copyResource("plus.svg",dname);
1276+
mgr.copyResource("minus.svg",dname);
1277+
mgr.copyResource("plusd.svg",dname);
1278+
mgr.copyResource("minusd.svg",dname);
12601279
}
12611280

12621281
void HtmlGenerator::writeSearchData(const QCString &dname)
@@ -2803,19 +2822,32 @@ static void writeDefaultQuickLinks(TextStream &t,bool compact,
28032822
<< (serverBasedSearch?"true":"false") << ",'"
28042823
<< searchPage << "','"
28052824
<< theTranslator->trSearch() << "');\n";
2825+
QCString initCodeFold;
2826+
if (Config_getBool(HTML_CODE_FOLDING))
2827+
{
2828+
initCodeFold="init_codefold();";
2829+
}
28062830
if (Config_getBool(SEARCHENGINE))
28072831
{
28082832
if (!serverBasedSearch)
28092833
{
2810-
t << " $(document).ready(function() { init_search(); });\n";
2834+
t << " $(document).ready(function() { init_search(); " << initCodeFold << "});\n";
28112835
}
28122836
else
28132837
{
28142838
t << " $(document).ready(function() {\n"
2815-
<< " if ($('.searchresults').length > 0) { searchBox.DOMSearchField().focus(); }\n"
2816-
<< " });\n";
2839+
<< " if ($('.searchresults').length > 0) { searchBox.DOMSearchField().focus(); }\n";
2840+
if (!initCodeFold.isEmpty())
2841+
{
2842+
t << " " << initCodeFold << "\n";
2843+
}
2844+
t << " });\n";
28172845
}
28182846
}
2847+
else if (initCodeFold.isEmpty())
2848+
{
2849+
t << " $(document).ready(function() { " << initCodeFold << "});\n";
2850+
}
28192851
t << "});\n";
28202852
t << "/* @license-end */\n";
28212853
t << "</script>\n";

src/htmlgen.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ class HtmlCodeGenerator
5151
void writeCodeAnchor(const QCString &anchor);
5252
void startCodeFragment(const QCString &style);
5353
void endCodeFragment(const QCString &);
54+
void startFold(int,const QCString &,const QCString &);
55+
void endFold();
5456

5557
void setRelativePath(const QCString &path);
5658
private:

0 commit comments

Comments
 (0)