[solution] Easy way to strip comments? lazarus / 9-Editor ---------------------------------------------------------------------- indydev · 2024-05-29 17:24 Maybe I missed something, but is there a way, in the Lazarus IDE to either copy, or save a unit stripped of all of its comments? For the inevitable question 'why?':  Because sometimes I get units loaded with unhelpful comments; sometimes I need to send something with comments only for me that another person will not need; to help train AI models etc. The idea isn't to remove comments in a project, but to have a way to strip out for uses beyond the project you are working on. ---------------------------------------------------------------------- cdbc · 2024-05-29 19:55 Hi I think I can whip something together in a jiffy, hang on... edit: I'm back... With a quick'n'dirty console app, it's got some beauty-flaws, but it's a first try, so bear with me  It could perhaps also do with a bit of debugging, but it's getting late, here in Denmark... Let me know what you think...  Regards Benny ---------------------------------------------------------------------- cdbc · 2024-05-30 01:06 Hi Right, couldn't help myself...  Here's a new version, with some bug-fixes Should be better, let me know, what you think  edit: I call it like this:  "./strip_pas_comments comment_states.pas > strip.pas" /without the quotes/ Regards Benny ---------------------------------------------------------------------- indydev · 2024-05-30 02:05 Thanks.  I had some trouble getting back on here for some reason, and I decided to write my own. Will check yours. Here's my command line version that works "ok" for now. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program stripcmts; {$mode objfpc}{$H+} uses  {$IFDEF UNIX}  cthreads,  {$ENDIF}  Classes, SysUtils, CustApp,  RegExpr; type   { TMyApplication }   TMyApplication = class(TCustomApplication)  protected    procedure DoRun; override;  public    constructor Create(TheOwner: TComponent); override;    procedure WriteHelp; virtual;  end; var  FileName: String;  FileContent: TStringList;  RegEx: TRegExpr;  i: integer; { TMyApplication } procedure TMyApplication.DoRun;var  ErrorMsg: String;begin  // quick check parameters  ErrorMsg:=CheckOptions('h', 'help');  if ErrorMsg<>'' then begin    ShowException(Exception.Create(ErrorMsg));    Terminate;    Exit;  end;   // parse parameters  if HasOption('h', 'help') then begin    WriteHelp;    Terminate;    Exit;  end;   if ParamCount < 1 then  begin    WriteLn('Usage: ./stripcmts ');    Terminate;    Exit;  end;   FileName := ParamStr(1);  FileContent := TStringList.Create;  try    FileContent.LoadFromFile(FileName);     RegEx := TRegExpr.Create;    RegEx.ModifierM:=TRUE;     try      RegEx.Expression := '\/\/.*';      RegEx.ModifierI := TRUE; // case- insensitive       for i := 0 to FileContent.Count - 1 do      begin      if RegEx.Exec(FileContent[i]) then FileContent[i] := StringReplace(FileContent[i], RegEx.Match[0], '', []);      end;     RegEx.Expression := '\{\s*[^\s\$].*?\}'; //'\{[^\$].*?\}';      RegEx.ModifierS := TRUE;      FileContent.Text := RegEx.Replace(FileContent.Text, '', FALSE);       finally      RegEx.Free;    end;     FileContent.SaveToFile(ChangeFileExt(FileName, '_CmtFree.pas'));  finally    FileContent.Free;  end;  // stop program loop  Terminate;end; constructor TMyApplication.Create(TheOwner: TComponent);begin  inherited Create(TheOwner);  StopOnException:=True;end; procedure TMyApplication.WriteHelp;begin  { still need to write help code here }  writeln('Usage: ', ExeName, ' -h');end; var  Application: TMyApplication;begin  Application:=TMyApplication.Create(nil);  Application.Title:='Strip Comments';  Application.Run;  Application.Free;end. need to add '(*' '*)' blocks to be more complete, and this doesn't handle nested blocks. Thanks for writing a couple up. ---------------------------------------------------------------------- indydev · 2024-05-30 02:19 Quote from: cdbc on May 30, 2024, 03:06:34 amHi Right, couldn't help myself...  Here's a new version, with some bug-fixes Should be better, let me know, what you think  edit: I call it like this:  "./strip_pas_comments comment_states.pas > strip.pas" /without the quotes/ Regards Benny Ok. You went to town on this. I just put some regular expressions together. I didn't bother with a more advanced state machine. Thanks for your work! ---------------------------------------------------------------------- Thaddy · 2024-05-30 05:29 I like both approaches. Personnally I have been using Dipp by Ralf Junker for decades. It is freeware, also works with FreePascal code, but he never releases sourcecode, which is a pity. You can get it from his website. I hope both of you will look at "what is missing"?. Just a teaser, https://www.yunqa.de/delphi/apps/dipp/index ---------------------------------------------------------------------- cdbc · 2024-05-30 08:09 Hahaha Thaddy  Thanks for that one...  I bet, 'Dipp' isn't a 3 hour job  I quite enjoyed the break from other stuff  Regards Benny ---------------------------------------------------------------------- indydev · 2024-05-30 14:25 Quote from: Thaddy on May 30, 2024, 07:29:48 amI like both approaches. Personnally I have been using Dipp by Ralf Junker for decades. It is freeware, also works with FreePasccal code, but he never releases sourcecode, which is a pity. You can get it from his website. I hope both of you will look at "what is missing"?. Just a teaser, https://www.yunqa.de/delphi/apps/dipp/index Unfortunately, this is Windows only.  I just don't have ready access to try it (a situation I need to resolve soon), let alone use it. ---------------------------------------------------------------------- indydev · 2024-05-30 14:28 Ok. Looks like there was some interest. I went ahead and completed my attempt to make it at least a little more useable. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program stripcmts; {$mode objfpc}{$H+} uses  {$IFDEF UNIX}  cthreads,  {$ENDIF}  Classes, SysUtils, CustApp, Process,  RegExpr; type   { TStripComments }   TStripComments = class(TCustomApplication)  protected    procedure DoRun; override;  public    constructor Create(TheOwner: TComponent); override;    procedure WriteHelp; virtual;  end; var  FileName, NewFileName, OpenProgram: String;  FileContent: TStringList;  RegEx: TRegExpr;  i: integer; { TStripComments } procedure TStripComments.DoRun;var  ErrorMsg: String;   procedure RemoveExtraBlankLines(CodeLines: TStringList);  var    i: integer;    BlankLineFound: Boolean;  begin    BlankLineFound := False;    i := 0;    while i < CodeLines.Count do  begin      if Trim(CodeLines[i]) = '' then      begin        if BlankLineFound then CodeLines.Delete(i) else        begin          BlankLineFound := True;          Inc(i);        end;      end else    begin        BlankLineFound := False;        Inc(i);      end;    end;  end; begin  // quick check parameters  ErrorMsg:=CheckOptions('h', 'help');  if ErrorMsg<>'' then begin    ShowException(Exception.Create(ErrorMsg));    Terminate;    Exit;  end;   // parse parameters  if HasOption('h', 'help') then begin    WriteHelp;    Terminate;    Exit;  end;   if ParamCount < 1 then  begin    WriteLn('Usage: ./stripcmts [program to open unit]');    WriteLn('The second parameter is optional');    Terminate;    Exit;  end;   FileName := ParamStr(1);  if ParamCount >= 2 then    OpenProgram := ParamStr(2)  else    OpenProgram := 'xed';  // default program   FileContent := TStringList.Create;  try    FileContent.LoadFromFile(FileName);     RegEx := TRegExpr.Create;    RegEx.ModifierM:=TRUE;     try      RegEx.Expression := '\/\/.*';    RegEx.ModifierI := TRUE; // case-insensitive       for i := 0 to FileContent.Count - 1 do      begin        if RegEx.Exec(FileContent[i]) then FileContent[i] := StringReplace(FileContent[i], RegEx.Match[0], '', []);      end;     RegEx.Expression := '\{\s*[^\s\$].*?\}|\(\*.*\*\)';      RegEx.ModifierS := TRUE;      FileContent.Text := RegEx.Replace(FileContent.Text, '', FALSE);     finally      RegEx.Free;    end;     // Remove extra blank lines    RemoveExtraBlankLines(FileContent);     NewFileName := ChangeFileExt(FileName, '_CmtFree.pas');    FileContent.SaveToFile(NewFileName);     // Launch the new file in xed editor    with TProcess.Create(nil) do    try      Executable := OpenProgram;      Parameters.Add(NewFileName);      Options := Options + [poWaitOnExit];      Execute;    finally      Free;    end;   finally    FileContent.Free;  end;  // stop program loop  Terminate;end; constructor TStripComments.Create(TheOwner: TComponent);begin  inherited Create(TheOwner);  StopOnException:=True;end; procedure TStripComments.WriteHelp;begin  writeln('Usage: ', ExeName, ' [program to open unit]');  writeln;  writeln('Description:');  writeln('  This program strips comments from a Free Pascal source file.');  writeln('  It supports single-line comments (//), multi-line comments ({...}),');  writeln('  and Pascal-style block comments ( (* ... *) ). The cleaned file');  writeln('  is saved with a "_CmtFree" suffix added to the original filename.');  writeln;  writeln('Parameters:');  writeln('               The name of the file to process.');  writeln('  [program to open unit] Optional. The program to use to open the cleaned file.');  writeln('                         If not specified, the default program "xed" will be used.');  writeln;  writeln('Examples:');  writeln('  ', ExeName, ' myfile.pas');  writeln('    Strips comments from "myfile.pas" and opens the result with the default program.');  writeln;  writeln('  ', ExeName, ' myfile.pas nano');  writeln('    Strips comments from "myfile.pas" and opens the result with "codeeditor".');end;  var  Application: TStripComments;begin  Application:=TStripComments.Create(nil);  Application.Title:='Strip Comments';  Application.Run;  Application.Free;end. ---------------------------------------------------------------------- Roland57 · 2024-05-30 14:53 @cdbc Nice! And seems to work well. @indydev I will try your latest version. I love regular expressions. I enter the competition with my Pascal Code Cleaner written in Lua.  Usage: lua pcc.lua SOURCE DESTINATION [DEBUGFILE1] [DEBUGFILE2] Where all parameters are file names. Parameters 3 and 4 are optional. ---------------------------------------------------------------------- alpine · 2024-05-30 15:10 Given the regex used, why not just use sed or awk for that? ... or Shift+Ctrl+F in Lazarus? ---------------------------------------------------------------------- cdbc · 2024-05-30 17:38 Hi @indydev: Nifty little program, Me Likey  seems to also work well and nice touch, with the opening in an editor for review \o/ @Roland57: Yours is nice too  works well and although it's the first time I see Lua, I can follow the code \o/ Cool - Benny ---------------------------------------------------------------------- indydev · 2024-05-30 17:49 Quote from: alpine on May 30, 2024, 05:10:24 pmGiven the regex used, why not just use sed or awk for that? ... or Shift+Ctrl+F in Lazarus? Dependencies! Well, at least that was my thought. I have written bash scripts to do work for myself only to find later I needed it somewhere else. While my code is geared toward Linux, it is easily converted for use in Windows. I haven't been on Windows in years, but does it come with sed or awk? ---------------------------------------------------------------------- cdbc · 2024-05-30 21:36 Hi Saw you've noticed my 'IStringList', here's the lpr-file using the IStringList...:Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program strip_pas_comments;{$mode objfpc}{$H+}{ ©2024 Benny Christensen a.k.a. cdbc, All right reserved }uses  Classes, sysutils, StrUtils, istrlist, bc_statemachine, comment_states;type  { TStateData provides an object for our event-handler, we don't need to instantiate :o) }  TStateData = object    procedure HandleStateData(aSender: TbcState;aFieldList: TStrings;var {%H-}Cancel: boolean);  end; var  raw: boolean = false; // suppresses the writing of 'filename', '<- - ->' & 'Done!'  Sd: TStateData; // works just fine  sl: IStringList; // now using the *new* IStringList  Sm: TbcStateMachine;  Statenames: TStringArray;  pasFile: string; { TStateData }procedure TStateData.HandleStateData(aSender: TbcState;aFieldList: TStrings;var Cancel: boolean);var  S: string;begin  case IndexStr(aSender.ClassName,Statenames) of    0: ; { nothing to do in idle }    1: for S in aFieldList do         if S <> '' then writeln(S);    2: for S in aFieldList do         if S <> '' then writeln(S);    3: for S in aFieldList do         if S <> '' then writeln(S);    4: for S in aFieldList do         if S <> '' then writeln(S);    5: ; { nothing to do in error }    else      writeln('not in case -> ',trim(aFieldList.Text));  end;end; begin  if ParamCount < 1 then begin    writeln('* * * please provide a filename in 1.st param * * *');    writeln('* * * optionally provide -r in 2.nd param for raw output * * *');    Halt(-1);  end;  pasFile:= ParamStr(1);  if ParamCount >= 2 then raw:= (ParamStr(2) = '-r');  if not raw then begin    writeln('Parsing pascal file: ',pasFile); // has been made optional    writeln('<- - - - ->'); // has been made optional  end;  Sl:= CreateStrList; // now making use of the *new* IStringList  Sl.LoadFromFile(pasFile);  Sm:= TbcStateMachine.Create;  try    RegisterPASStates(Sm,StateNames); { convenience procedure in comment_states }    Sm.OnStateData:= @{%H-}Sd.HandleStateData; { event-handler for getting at the data }    Sm.ExecuteFile(Sl.List);      { unleash the beast on the poor file :-D }  finally Sm.Free; end;  if not raw then writeln('Done!'); // has been made optionalend. Added also the '-r' option to get /raw/ output. For others who'd like to try 'IStringList' out, you can get it here:  https://gitlab.com/cdbc-public/ibcstringlist. Regards Benny ---------------------------------------------------------------------- cdbc · 2024-05-31 15:10 Hi If anyone should be interested, I found a bug in 'comment_states.pas', that /eats/ Guids, as if they were comments... Attached is a bug-fixed unit. Regards benny ---------------------------------------------------------------------- indydev · 2024-05-31 15:29 Are Guids not always between `'` `'`s? If not, mine will also have that problem. ---------------------------------------------------------------------- indydev · 2024-05-31 15:36 I keep coming back to this. So let's get it into the IDE. Can we make a package? Removed error prone code. Starting over... ---------------------------------------------------------------------- cdbc · 2024-05-31 16:23 Hi Huh...  I've never made a package, so I'm a bit baron in that region... Maybe someone can jump in with a magic wand... Sorry mate, haven't got the foggiest... Regards Benny ---------------------------------------------------------------------- Roland57 · 2024-05-31 16:49 Quote from: indydev on May 31, 2024, 05:36:35 pmI keep coming back to this. So let's get it into the IDE. Can we make a package? I never made a package myself, so I cannot help. You could also compile it as a simple console application and add it in Lazarus tools menu. Some ideas in this discussion. ---------------------------------------------------------------------- WooBean · 2024-05-31 17:14 A natural way to add a feature "Strip of comments" would be modyfing a package JEDI Code Format. A GUI interface will be like a picture below: ---------------------------------------------------------------------- indydev · 2024-05-31 22:15 Well, I got a unit to finally compile and rebuilt the IDE, but it seems nothing happened. I just don't know what I am doing. I need to move on to other things for now. I post the code for someone who might come along and figure it out. stripcomments.pas Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i unit StripComments; {$mode objfpc}{$H+} interface uses  Classes, SysUtils, Menus, IDECommands, SrcEditorIntf, IDEMsgIntf, RegExpr, IDEExternToolIntf, LazIDEIntf, MenuIntf; procedure Register; implementation type  TStripComments = class  public    procedure AddPopupMenuItem(Sender: TObject);  end; procedure TStripComments.AddPopupMenuItem(Sender: TObject);var  SourceEditor: TSourceEditorInterface;  FileName, NewFileName: String;  FileContent: TStringList;  RegEx: TRegExpr;  i: Integer;   procedure RemoveExtraBlankLines(CodeLines: TStringList);  var    i: Integer;    BlankLineFound: Boolean;  begin    BlankLineFound := False;    i := 0;    while i < CodeLines.Count do    begin      if Trim(CodeLines[i]) = '' then      begin        if BlankLineFound then          CodeLines.Delete(i)        else        begin          BlankLineFound := True;          Inc(i);        end;      end      else      begin      BlankLineFound := False;        Inc(i);      end;    end;  end; begin  SourceEditor := SourceEditorManagerIntf.ActiveEditor;  if Assigned(SourceEditor) then  begin    FileName := SourceEditor.FileName;    FileContent := TStringList.Create;    try    FileContent.Text := SourceEditor.SourceText;       RegEx := TRegExpr.Create;      RegEx.ModifierM := True;      try        // Strip single-line comments        RegEx.Expression := '\/\/.*';      RegEx.ModifierI := True; // case-insensitive         for i := 0 to FileContent.Count - 1 do        begin          if RegEx.Exec(FileContent[i]) then            FileContent[i] := StringReplace(FileContent[i], RegEx.Match[0], '', []);        end;       // Strip multi-line and Pascal-style block comments        RegEx.Expression := '[^'']\{\s*[^\s\$].*?\}|\(\*.*?\*\)';        RegEx.ModifierS := True;        FileContent.Text := RegEx.Replace(FileContent.Text, '', False);       finally        RegEx.Free;      end;       // Remove extra blank lines      RemoveExtraBlankLines(FileContent);       // Save the stripped content to a new file      NewFileName := ChangeFileExt(FileName, '_CmtFree.pas');      FileContent.SaveToFile(NewFileName);       // Provide some progress messages      IDEMessagesWindow.AddCustomMessage(mluNone, 'Comments stripped and saved to: ' + NewFileName);      IDEMessagesWindow.AddCustomMessage(mluNone, 'Opening the new file...');       // Open the new file in the IDE      LazarusIDE.DoOpenEditorFile(NewFileName, -1, -1, [ofAddToRecent]);   finally      FileContent.Free;    end;  end  else  begin    IDEMessagesWindow.AddCustomMessage(mluError, 'No active source editor found.');  end;end; procedure AddMenu;var  ParentMenu: TIDEMenuSection;  StripComments: TStripComments;begin  StripComments := TStripComments.Create;  // Find or create the parent menu section  ParentMenu := RegisterIDEMenuSection(mnuEdit, 'StripCommentsSection');   // Register the new menu item in the existing editor popup menu section  RegisterIDEMenuCommand(ParentMenu, 'StripOutComments', 'Remove Comments', @StripComments.AddPopupMenuItem);end; procedure Register;begin  AddMenu;end; end. In my attempt to limit problems I stopped using IStringList, but I don't think it played a role in my difficulties. and strpcomments.lpk Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i                                                                                                                                                                               ---------------------------------------------------------------------- alpine · 2024-06-01 05:12 BTW,  Lazarus already have search/replace by regex, including in files and directories. The only thing is how to make a search item in the MRU list permanent. ---------------------------------------------------------------------- JuhaManninen · 2024-06-01 06:16 Quote from: indydev on June 01, 2024, 12:15:09 amWell, I got a unit to finally compile and rebuilt the IDE, but it seems nothing happened. I just don't know what I am doing. I need to move on to other things for now. I post the code for someone who might come along and figure it out. I made a package using your code and installed it. Comments get stripped out as advertised! Only the new '*_CmtFree.pas' file is not found and opened in editor initially. You must switch active program and then do the same thing in Lazarus again. Then it is opened. Calling LazarusIDE.DoOpenEditorFile(NewFileName, -1, -1, [ofAddToRecent]); after FileContent.Free; makes no difference. Even adding a Sleep(1000) does not help. It is a feature or bug in DoOpenEditorFile(). BTW, uploading your package here zipped instead of copy/pasting individual file contents would make testing easier. ---------------------------------------------------------------------- JuhaManninen · 2024-06-01 11:48 InvalidateFileStateCache(NewFilename); before LazarusIDE.DoOpenEditorFile() fixes the issue. Unit LazFileCache must be added to uses section. This is not intuitive. I try to get DoOpenEditorFile() fixed so that no explicit InvalidateFileStateCache() call is needed. ---------------------------------------------------------------------- indydev · 2024-06-01 14:55 Hmmm. I can't seem to get this over the finish line. I have made the changes you listed, but I still get the Unable to read file "unit1_cmtfree.pas". message. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i ...      // Open the new file in the IDE      IDEMessagesWindow.AddCustomMessage(mluNone, 'Comments stripped and saved to: ' + NewFileName);      IDEMessagesWindow.AddCustomMessage(mluNone, 'Opening the new file...');     finally      FileContent.Free;    end;    InvalidateFileStateCache(NewFilename);     // Newly added as suggested    LazarusIDE.DoOpenEditorFile(NewFileName, -1, -1, [ofAddToRecent]);  // Moved to be after FileContent.Free  end  else  begin    IDEMessagesWindow.AddCustomMessage(mluError, 'No active source editor found.');  end;end;  I have made some changes to the RegEx.Expression. For some reason I had removed the compiler directive check and the `'` string check was never added. It should be: RegEx.Expression := '[^'']\{\s*[^\s\$].*?\}|\(\*.*?\*\)'; When I can get this to fully work, I will follow your suggestion to zip it all up and upload it ...and maybe make a gitlab page. Thank you so much for helping out here! It is really appreciated. ---------------------------------------------------------------------- lainz · 2024-06-01 15:51 Next add-on, autocomplete comments with AI. ---------------------------------------------------------------------- indydev · 2024-06-01 17:27 Quote from: indydev on June 01, 2024, 04:55:40 pmI have made some changes to the RegEx.Expression. For some reason I had removed the compiler directive check and the `'` string check was never added. It should be: RegEx.Expression := '[^'']\{\s*[^\s\$].*?\}|\(\*.*?\*\)'; Another expression change. If you set the 2nd regular expression to: RegEx.Expression := '[^'']\{\s*[^\s\$].*?\}|\(\*.*?\*\)|\/\/([^\n\r])+'; You can remove the single-line comment block above it. Still trying to get the package to fully work. It removes the comments and makes the file, just struggles to open the unit--at least for me. ---------------------------------------------------------------------- DomingoGP · 2024-06-01 20:22 Maybe you can try another aproach to open the modified code in the ide. ( not tested here, but works in my package https://github.com/DomingoGP/lazIdeDiffCompareFiles ). Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses ,  projectintf;    //<< Add to uses for FileDescriptorText.   LazarusIDE.DoNewEditorFile(FileDescriptorText, NewFileName, FileContent.Text, [nfOpenInEditor, nfIsNotPartOfProject]); ---------------------------------------------------------------------- indydev · 2024-06-01 22:06 That worked!! Thank you so much. Cleaning things up, and testing other menu locations so anyone can put it where they want. Have some obligations so won't get to it until tomorrow. Of course anyone can take the information here and put it together on their own if they wish. ---------------------------------------------------------------------- Roland57 · 2024-06-03 06:25 Hello! FYI, after I installed the package (Linux, Lazarus 3.2), I could no longer start Lazarus (access violation). Maybe I made a wrong manipulation... ---------------------------------------------------------------------- indydev · 2024-06-03 14:56 See Updated Version in later post. ---------------------------------------------------------------------- Roland57 · 2024-06-03 16:02 Congratulations for the clean package example (not yet tested the latest version). For the bugs that you reported (comments in strings, strings in comments), I think it's impossible to do perfectly with regular expressions. ---------------------------------------------------------------------- indydev · 2024-06-03 16:46 Quote from: Roland57 on June 03, 2024, 06:02:10 pmFor the bugs that you reported (comments in strings, strings in comments), I think it's impossible to do perfectly with regular expressions.  Braced comments in strings and braced comments inside braced comments. Strings in comments are fine. But yes, without negative lookback in TRegExpr you are right (maybe even with negative lookback because you could have many nested braced comments). Working on a loop through characters version. I discovered that some editors don't handle nested braced comments so you wouldn't have them in those editors, but the Lazarus editor does, so I want to handle that case. Thanks for pointing out the error! I had many typos in the write up, and filenames and... ugh. ---------------------------------------------------------------------- indydev · 2024-06-03 19:46 StripComments is a Lazarus IDE package that adds a menu item for removing comments from the currently open unit in the editor. This package strips out line comments and block comments from an Object Pascal unit (both `{ }` and `(* *)` comment styles).  Handles nested `{ { } }` blocks.GUIDs are safe.Compiler directives are safe.JSON strings are safe.Regular Expressions are safe.`{ }` inside string literals are safe. Installation 1. Unzip stripcomments.zip: 2. Navigate to the directory containing your package:     cd path/to/your/package 3. Open a terminal and execute the following commands:     lazbuild strpcomments.lpk         lazbuild --add-package strpcomments.lpk         lazbuild --build-ide= --build-mode= Usage After installation, the "Remove Comments" option will be available in the Lazarus IDE under the "Edit" menu by default, but you have the option to place it in the "Source" menu, the source editor popup menu, or all of them by uncommenting those options in the code. The keyboard shortcut Ctrl+Shift+R will also perform the "Remove Comments" menu item. License This project is licensed under the MIT License. See the LICENSE inside unit file for details. Warranty None. Code is available for you. Will try to fix installation errors, and may add additional fixes if they affect my own work. Thanks to cdbc, JuhaManninen, DomingoGP and Roland57 for encouragement, tips, and pointing out fails! ---------------------------------------------------------------------- Ed78z · 2025-12-30 00:49 Quote from: indydev on May 29, 2024, 07:24:19 pmMaybe I missed something, but is there a way, in the Lazarus IDE to either copy, or save a unit stripped of all of its comments? For the inevitable question 'why?':  Because sometimes I get units loaded with unhelpful comments; sometimes I need to send something with comments only for me that another person will not need; to help train AI models etc. The idea isn't to remove comments in a project, but to have a way to strip out for uses beyond the project you are working on. Try my PureCode utility program if you like