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.