This is CEClassEditor+FileHandling.m in view mode; [Download] [Up]
/* CEClassEditor+FileHandling.m * * This object controls the data of a beaker (molecules, cameras, groups etc.) * It is the main document of BeakerBoy and controls everything from loading to * setting up the browser which does most of the other work. * * For interface-info see the header file. The comments in this file mostly * cover only the real implementation details. * * Written by: Thomas Engel * Created: 23.10.1993 (Copyleft) * Last modified: 12.11.1994 */ #import <CEClassEditor+FileHandling.h> #import <CEMethod.h> #import <Text_MiscExtensions.h> #import <MiscSources.subproj/MiscStringArray_List.h> #import <MiscSources.subproj/MiscEmacsText.h> #import <misckit/MiscString.h> #import <misckit/MiscStringArray.h> #import <misckit/MiscSortedList.h> @implementation CEClassEditor ( FileHandling ) - _try:path withAlternatives:anArray forText:aText andGetStyle:(int *)aStyle andSetPath:realPath { id ourPath; id theFilename; id aSearchPath; int trys; trys = 0; *aStyle = CE_FILETYPE_NONE; ourPath = [path copy]; theFilename = [ourPath fileName]; while( *aStyle == CE_FILETYPE_NONE && trys <= [anArray count] ) { [self _readFile:ourPath forText:aText andGetStyle:(int *)aStyle andSetPath:realPath]; aSearchPath = [anArray objectAt:trys]; [ourPath free]; ourPath = nil; if( aSearchPath != nil ) { // If it is a dot file then we should try the original path // again...actualle we shoul just skip it...I know..but this // works too. if( [aSearchPath cmp:"."] == 0 ) ourPath = [path pathName]; else if( [aSearchPath spotOfStr:"./"] == 0 ) { ourPath = [path pathName]; [ourPath addChar:[MiscString pathSeparator]]; [ourPath concatenate:aSearchPath]; // NOTE: We have to remove the local Dot reference // because somehow it does not work !! [ourPath replaceEveryOccurrenceOf:"/./" with:"/"]; } else ourPath = [aSearchPath copy]; [ourPath addChar:[MiscString pathSeparator]]; [ourPath concatenate:theFilename]; } trys++; } if( ourPath != nil ) [ourPath free]; [theFilename free]; return self; } - _readFile:path forText:aText andGetStyle:(int *)aStyle andSetPath:realPath { // We will try to load the requested file...and try to set the right // text type info. NXStream * aStream; // By default we won't allow anybody to edit a none existing text. // It won't get stored anyway because we do not have a path for // it. [aText setEditable:NO]; [path replaceTildeWithHome]; if( [path doesExistInFileSystem] == NO ) return self; // Looks like we have a file...so lets get it into our text object. // We have to ensure that all the setting are in the proper fashion. // Setting the tabs for ASCII Files in one of them if( [self _isFileRTF:path] ) { *aStyle = CE_FILETYPE_RTF; [aText setMonoFont:NO]; } else { *aStyle = CE_FILETYPE_ASCII; [aText setMonoFont:YES]; } [aText setEditable:YES]; aStream = NXMapFile( [path stringValue], NX_READONLY ); if( *aStyle == CE_FILETYPE_ASCII ) [aText readText:aStream]; else [aText readRichText:aStream]; NXCloseMemory( aStream, NX_FREEBUFFER ); // Now lets remember where we really did load it from. [realPath takeStringValueFrom:path]; return self; } - (BOOL)_isFileRTF:path { // Tells us if the requested file is a RTF text. // Lets get the first characters and test for "{\rtf0" // If it is there...we should load the same text as RTF again. // Well quite stupid code.. FILE * aStream; char magic[10]; id aString; BOOL answer; magic[6] = 0; answer = NO; if( [path doesExistInFileSystem] ) { aStream = fopen( [path stringValue], "r" ); fgets( magic, 7, aStream ); fclose( aStream ); aString = [MiscString newWithString:magic]; if( [aString cmp:"{\\rtf0"] == 0 ) answer = YES; [aString free]; } return answer; } - (const char *)filename { return [filename stringValue]; } /* * Here come the methods we will implement to satisfy our window. * They are useful to change the inspector and handle oher tasks. */ - _parseMethodFile { id newLine; id newMethod; int lines; int i; lines = [headerFile lineFromPosition:[headerFile textLength]]; for( i=1; i<lines; i++ ) { newLine = [headerFile substringFromLine:i]; if ( [newLine charAt:0] == '-' || [newLine charAt:0] == '+' ) { newMethod = [[CEMethod alloc] initFromText:newLine]; [methodList addObject:newMethod]; } [newLine free]; } return self; } - _writeText:aText withStyle:(int)aStyle to:path { // If the file does no exist...we will create it. // But it there is no real text for that file...we won't save anything ! // RTFD requires a differnt style of saving ! FILE * dummyStream; NXStream * aStream; if( aStyle == CE_FILETYPE_NONE ) return self; else if( aStyle == CE_FILETYPE_RTFD ) return self; else { // Well the streams won't truncate a file so we have to do it on // our own. // We should do a backup of existing files here if requested. dummyStream = fopen( [path stringValue], "w" ); if( dummyStream ) fclose( dummyStream ); aStream = NXMapFile( [path stringValue], NX_WRITEONLY ); switch( aStyle ) { case CE_FILETYPE_RTF: [aText writeRichText:aStream]; break; default: [aText writeText:aStream]; } NXSaveToFile( aStream, [path stringValue] ); NXCloseMemory( aStream, NX_FREEBUFFER ); } return self; } - (BOOL)_selectSourceForMethod:aMethod { // This private method searches for the method inside the SourceFile // and _selects_ the code-portion of this method. // This really is only for finding the part. // Returns success and sets the internal range information. // Now the text[] part is more then just ugly..sorry. char text[10000]; NXSelPt from; NXSelPt to; int i; int first; int last; BOOL gotIt; // Find the definition of the method. _sourceOriginStart = 0; _sourceOriginEnd = 0; [sourceFile setSel:0 :0]; gotIt = [sourceFile findText:[aMethod name] ignoreCase:NO backwards:NO wrap:NO]; if( ! gotIt ) return NO; // Now we assume that people stick to the rules and have the // '{' and '}' as the first characters of each line. [sourceFile getSel:&from :&to]; [sourceFile getSubstring:text start:from.cp length:9990]; // The first '{' is our start. A '0' terminates the string and the // search ! gotIt = NO; first = -1; for( i=0; i<9990; i++ ) { if( text[i] == 0 ) break; else if( text[i] == '{' ) { gotIt = YES; first = i; break; } } // Now we need a new-line followed by a '}'. A '0' terminates the string // and the search ! Same as before. gotIt = NO; last = -1; for( i=0; i<9990; i++ ) { if( text[i] == 0 ) break; else if( text[i] == '\n' && text[i+1] == '}' ) { gotIt = YES; last = i+2; break; } } // Now select the right area. if( gotIt ) { _sourceOriginStart = from.cp+first; _sourceOriginEnd = from.cp+last; [sourceFile setSel:_sourceOriginStart :_sourceOriginEnd]; } return gotIt; } - checkDocumentation:sender { // Now this method tries to get the documentation up to date. // Missing parts will be inserted...etc.pp. id aString; id aMethod; int i; id aList; // Be care full. we have the remember all the Docu and Source settings // becaus ehte method s we use will change them !!! // If there is no docu we ask the app to provide the default template. if( docuStyle == CE_FILETYPE_NONE ) { [[NXApp delegate] copyClassDocuTemplate]; [docuFile setMonoFont:NO]; [docuFile setEditable:YES]; [docuFile selectAll:self]; [docuFile pasteFrom:_tempPb]; // Be sure that we now have the right text type and a valid // path to save the docu to. docuStyle = CE_FILETYPE_RTF; _docuPath = [filename copy]; [_docuPath cat:".rtf"]; // The main template does need some customization to be useful // First the version [docuFile setSel:0 :0]; if( [docuFile findText:"MyVersion" ignoreCase:NO backwards:NO wrap:NO] ) [docuFile replaceSel:"0.1"]; // Then the Date if( [docuFile findText:"1994" ignoreCase:NO backwards:NO wrap:NO] ) [docuFile replaceSel:"1995"]; // Now the Copyright part if( [docuFile findText:"MyCompany" ignoreCase:NO backwards:NO wrap:NO] ) [docuFile replaceSel:"ClassEditor"]; // Its time for the class name if( [docuFile findText:"MyClass" ignoreCase:NO backwards:NO wrap:NO] ) { aString = [filename fileName]; [docuFile replaceSel:[aString stringValue]]; [aString free]; } // Some header infos please if( [docuFile findText:"MyClass.h" ignoreCase:NO backwards:NO wrap:NO] ) { aString = [filename fileName]; [aString cat:".h"]; [docuFile replaceSel:[aString stringValue]]; [aString free]; } } // Now we should have a documentation...but lets see if there is one // for every method. If not we should try to find the right place // to place a template. // We add the in a sorted order. // Because the _addDocu method does not take care of ordering...we // have to sort things the other way around. aList = [MiscSortedList new]; [aList setSortOrder:Misc_DESCENDING]; [aList setSortEnabled:YES]; for( i=0; i<[methodList count];i++ ) [aList addObject:[methodList objectAt:i]]; for( i=0; i<[aList count];i++ ) { aMethod = [aList objectAt:i]; if( [self _selectDocuForMethod:aMethod] == 0 ) [self _addDocuForMethod:aMethod]; } [aList free]; // That's it for today.. return self; } - (BOOL)_selectDocuForMethod:aMethod { // This private method searches for the method inside the DocuFile // and _selects_ the docu-portion of this method. // This really is only for finding the part. // Returns the offset to the description text. An offset of '0' meens that // no method descript was found. // "See also:" is included..AND the blank lines below. // BUG: Now the text[] part is more then just ugly..sorry. // This limits us to methods with descriptions shorter then approx 100 // lines. I hope that is enough. // I really should take the time and witch to a NXStream.... char text[10000]; NXSelPt from; NXSelPt to; NXSelPt tmpFrom; NXSelPt tmpTo; int first; int last; int i; BOOL gotIt; int blanks; int instancesStart = 0; id aString; // We have to find the right start-selection inside the documentation // first. This depends on the type of method. _docuOriginStart = 0; _docuOriginEnd = 0; [docuFile setSel:0 :0]; gotIt = [docuFile findText:"Instance Methods" ignoreCase:NO backwards:NO wrap:NO font:[Font newFont:"Helvetica-Bold" size:18]]; [docuFile getSel:&tmpFrom :&tmpTo]; if( gotIt ) instancesStart = tmpFrom.cp; // If the method is a class method we should search for the right start // pos. The instanceStart is necessary anyway..because we need to check if // the found selector belongs to the right category. // Becasue this problem can only happen when we search for a class method // (they come first) we should be save that way. if( ![aMethod isInstanceMethod] ) { [docuFile setSel:0 :0]; gotIt = [docuFile findText:"Class Methods" ignoreCase:NO backwards:NO wrap:NO font:[Font newFont:"Helvetica-Bold" size:18]]; } if( !gotIt ) return NO; // Well finding the right section is not enough.. we have to find the // right selector too. And sure...the font has to be correct. We // Don't want the find every ref to that method. // We also add a newline character to the end of the selector // because we are searching for the whole selector..and not // for a prefix inside another method. aString = [MiscString newWithString:[aMethod selectorName]]; [aString cat:"\n"]; gotIt = [docuFile findText:[aString stringValue] ignoreCase:NO backwards:NO wrap:NO font:[Font newFont:"Helvetica-Bold" size:14]]; [aString free]; if( !gotIt ) return NO; // Well now we should have the first selector for that method. // Now if the docu was created with CM it should work if we grab for // those right numbers of linefeeds. // Really nasty. Don't look at it. [docuFile getSel:&from :&to]; // Now if the "class" method is inside the instance docu section // then we really don't have a method ! if( ![aMethod isInstanceMethod] && from.cp > instancesStart && instancesStart > 0 ) return NO; // Otherwise read the string...brrr...we really should have a stream here. [docuFile getSubstring:text start:from.cp length:9990]; // Now we need the 2 last new-lines. A '0' terminates the string and the // search ! This is how we find the end of the docu. gotIt = NO; last = -1; for( i=0; i<9990; i++ ) { if( text[i] == 0 ) break; else if( text[i] == '\n' && text[i+1] == '\n' && text[i+2] == '\n' ) { gotIt = YES; last = i; break; } } if( !gotIt ) return NO; // We always search for 2 blank lines (but they never come right after // each other..look at the styles!). If we don't find a newLine char // with the small Helvetica-Bold 3pt font then it seams like we have an // old docu style...this qives us one line less to search for. // A '0' terminates the string and the // search ! blanks = 1; if( [docuFile findText:"\n" ignoreCase:NO backwards:NO wrap:NO font:[Font newFont:"Helvetica-Bold" size:3]] == YES ) { [docuFile getSel:&tmpFrom :&tmpTo]; // now if that small line really was in the scope of our currnet // method..then we have to pass 2 blank lines. // Btw..last is always valid because otherwise we won't be here. if( tmpFrom.cp < from.cp+last ) blanks = 0; } gotIt = NO; first = -1; for( i=0; i<9990; i++ ) { if( text[i] == 0 ) break; else if( text[i] == '\n' && text[i+1] == '\n' ) { blanks++; if( blanks == 2 ) { gotIt = YES; first = i+2; break; } } } // Now select the right area. if( gotIt ) { _docuOriginStart = from.cp+first; _docuOriginEnd = from.cp+last; [docuFile setSel:_docuOriginStart :_docuOriginEnd]; } return gotIt; } - _addDocuForMethod:aMethod { // This method adds a template for the documentation of a certain method. // It DOES corrupt the pasteboard ! // BUG: We also don't care about sorting ! This is bad ! NXSelPt from; NXSelPt to; int i; int paramCount; BOOL gotIt; id tokens; id aString; // We have to find the right start-selection inside the documentation // first. This depends on the type of method. [docuFile setSel:0 :0]; if( [aMethod isInstanceMethod] ) gotIt = [docuFile findText:"Instance Methods" ignoreCase:NO backwards:NO wrap:NO font:[Font newFont:"Helvetica-Bold" size:18]]; else gotIt = [docuFile findText:"Class Methods" ignoreCase:NO backwards:NO wrap:NO font:[Font newFont:"Helvetica-Bold" size:18]]; if( !gotIt ) return self; // Now this is ugly...we should sort the methods alpahbetically !! // Buggy because we can only HOPE that nobody adds something behind the // "* Methods" section. [docuFile getSel:&from :&to]; [docuFile setSel:to.cp+2 :to.cp+2]; [[NXApp delegate] copyMethodDocuTemplate]; [docuFile pasteFrom:_tempPb]; [docuFile setSel:to.cp+2 :to.cp+2]; [docuFile findText:"_CE_myMethod" ignoreCase:NO backwards:NO wrap:NO]; [docuFile replaceSel:[aMethod selectorName]]; [docuFile findText:"_CE_myMethod" ignoreCase:NO backwards:NO wrap:NO]; [docuFile getSel:&from :&to]; [docuFile replaceSel:[aMethod name]]; // First lets fix the method prefix of the class methods and // remove the dulicate prefix" [docuFile setSel:from.cp-2 :from.cp-1]; if( [aMethod isInstanceMethod] ) { [docuFile findText:"- " ignoreCase:NO backwards:NO wrap:NO]; [docuFile delete:self]; } else { [docuFile replaceSel:"+"]; [docuFile findText:"+ " ignoreCase:NO backwards:NO wrap:NO]; [docuFile delete:self]; } // Now its time to fix all the fonts. // Lets tokenize the methods name. This will create a list of // all the parts that have a different font. // We need to know how many parms the method takes to get a simpler // algorithm for coosing the fonts. tokens = [aMethod methodTokens]; paramCount = [aMethod numberOfArguments]; for( i=0; i<[tokens count]; i++ ) { // Now select the new token...if we fail to find it..lets quit here. aString = [tokens objectAt:i]; if( [docuFile findText:[aString stringValue] ignoreCase:NO backwards:NO wrap:NO] == NO ) break; // Depending on the type of token we will choose a different font. // First check for casts and then for paramerters. if( [aString grep:")"] ) [docuFile setSelFont:[Font newFont:"Times-Roman" size:14]]; // If it is not a cast the situation is a little bit trickier. // Imagine a - (BOOL)delegate or a simple -free method. // The token will be a part of the selector and still have no // ':' attached because there ist no argument else if( [aString grep:":"] == NO && paramCount > 0 ) { [docuFile setSelFont:[Font newFont:"Times-Italic" size:14]]; } } // The tokens are bound to die now. [[tokens freeObjects] free]; return self; } @end /* * History: 13.01.95 Buh * * * Bugs: - ... */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.