From 9fe55b07fdf2449a082f84b653c60064df20c09b Mon Sep 17 00:00:00 2001 From: Yee Cheng Chin Date: Thu, 26 Oct 2023 11:44:17 -0700 Subject: [PATCH] Add basic tests for MacVim Add a few tests for MacVim using XCTest. Previously MacVim had essentially zero automatic tests. While we run the Vim tests in CI, those mostly exercise Vim features, not MacVim. These new tests are Objective C tests that are designed for MacVim-layer functionality instead. Only adding a few tests (both unit tests and integration tests) now to get started and set up a template for different types of tests but more will be added later. Since MacVim consists of a lot of macOS API calls, a lot of those functionality are not easy to test automatically as they rely on behaviors out of MacVim's control (e.g. full screen functionality). The goal here is not to get 100% test coverage, but to catch areas prone to have regressions, code with non-straightforward logic, functionality that are annoying to manually test (e.g. key input), code blocks that perform calculations and can be easily unit tested. Areas that would be harder to add tests for include integration with macOS, e.g. full screen or dual monitor, interacting with the windowing system, or things that require screenshots in order to properly validate. The goal here is to progressively add more tests as we touch different parts of MacVim to help catch regressions. Old code that works and isn't being touched will be lower priority as it's unlikely they will suddenly break. --- .github/workflows/ci-macvim.yaml | 16 +- src/MacVim/MMAppController.m | 157 ++++---- src/MacVim/MMBackend.m | 4 + src/MacVim/MacVim.h | 1 + src/MacVim/MacVim.xcodeproj/project.pbxproj | 202 ++++++++++- .../xcshareddata/xcschemes/MacVim.xcscheme | 105 ++++++ src/MacVim/MacVim.xctestplan | 32 ++ src/MacVim/MacVimTests/MacVimTests.m | 334 ++++++++++++++++++ .../MacVim_xcode8.xcodeproj/project.pbxproj | 202 ++++++++++- src/MacVim/Miscellaneous.h | 10 +- src/MacVim/Miscellaneous.m | 34 +- src/Makefile | 3 + 12 files changed, 1002 insertions(+), 98 deletions(-) create mode 100644 src/MacVim/MacVim.xcodeproj/xcshareddata/xcschemes/MacVim.xcscheme create mode 100644 src/MacVim/MacVim.xctestplan create mode 100644 src/MacVim/MacVimTests/MacVimTests.m diff --git a/.github/workflows/ci-macvim.yaml b/.github/workflows/ci-macvim.yaml index 9e6e3c7130..085ff3f636 100644 --- a/.github/workflows/ci-macvim.yaml +++ b/.github/workflows/ci-macvim.yaml @@ -87,6 +87,11 @@ jobs: # Later, we pass the --enable-sparkle_1 flag to configure to set the corresponding ifdef. ln -fhs Sparkle_1.framework src/MacVim/Sparkle.framework + # Sparkle shows a dialog asking if the user wants to check for updates on 2nd launch of + # MacVim. On Sparkle 1 this is annoyingly a modal dialog box and interferes with tests. + # Just disable it by pre-setting to not check for updates. + defaults write org.vim.MacVim SUEnableAutomaticChecks 0 + - name: Set up Xcode if: matrix.xcode != '' run: | @@ -310,7 +315,12 @@ jobs: echo 'MacVim_xcode8.xcodeproj is outdated. Run "make -C src macvim-xcodeproj-compat" to re-generate it.'; false fi - - name: Build test binaries + - name: Test MacVim + timeout-minutes: 10 + run: | + make ${MAKE_BUILD_ARGS} -C src macvim-tests + + - name: Build Vim test binaries run: | # Build the unit test binaries first. With link-time-optimization they take some time to link. Running them # separately de-couples them from the timeout in tests, and allow us to build in parallel jobs (since tests @@ -320,11 +330,11 @@ jobs: set -o verbose make ${MAKE_BUILD_ARGS} -j${NPROC} -C src unittesttargets - - name: Test + - name: Test Vim timeout-minutes: 20 run: make ${MAKE_BUILD_ARGS} test - - name: Test GUI + - name: Test Vim (GUI) timeout-minutes: 20 run: | make ${MAKE_BUILD_ARGS} -C src/testdir clean diff --git a/src/MacVim/MMAppController.m b/src/MacVim/MMAppController.m index 79c4996b93..9afacf7a72 100644 --- a/src/MacVim/MMAppController.m +++ b/src/MacVim/MMAppController.m @@ -121,6 +121,7 @@ - (NSArray *)filterOpenFiles:(NSArray *)filenames - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event replyEvent:(NSAppleEventDescriptor *)reply; #endif ++ (NSDictionary*)parseOpenURL:(NSURL*)url; - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event replyEvent:(NSAppleEventDescriptor *)reply; - (NSMutableDictionary *)extractArgumentsFromOdocEvent: @@ -523,30 +524,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification // If the current version is larger, set that to be stored. Don't // want to do it otherwise to prevent testing older versions flipping // the stored version back to an old one. - NSArray *lastUsedVersionItems = [lastUsedVersion componentsSeparatedByString:@"."]; - NSArray *currentVersionItems = [currentVersion componentsSeparatedByString:@"."]; - // Compare two arrays lexographically. We just assume that version - // numbers are also X.Y.Z… with no "beta" etc texts. - BOOL currentVersionLarger = NO; - for (int i = 0; i < currentVersionItems.count || i < lastUsedVersionItems.count; i++) { - if (i >= currentVersionItems.count) { - currentVersionLarger = NO; - break; - } - if (i >= lastUsedVersionItems.count) { - currentVersionLarger = YES; - break; - } - if (currentVersionItems[i].integerValue > lastUsedVersionItems[i].integerValue) { - currentVersionLarger = YES; - break; - } - else if (currentVersionItems[i].integerValue < lastUsedVersionItems[i].integerValue) { - currentVersionLarger = NO; - break; - } - } - + const BOOL currentVersionLarger = (compareSemanticVersions(lastUsedVersion, currentVersion) == 1); if (currentVersionLarger) { [ud setValue:currentVersion forKey:MMLastUsedBundleVersionKey]; @@ -2130,6 +2108,73 @@ - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event } #endif ++ (NSDictionary*)parseOpenURL:(NSURL*)url +{ + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + + // Parse query ("url=file://...&line=14") into a dictionary + NSArray *queries = [[url query] componentsSeparatedByString:@"&"]; + NSEnumerator *enumerator = [queries objectEnumerator]; + NSString *param; + while ((param = [enumerator nextObject])) { + // query: = + NSArray *arr = [param componentsSeparatedByString:@"="]; + if ([arr count] == 2) { + // parse field + NSString *f = [arr objectAtIndex:0]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 + f = [f stringByRemovingPercentEncoding]; +#else + f = [f stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; +#endif + + // parse value + NSString *v = [arr objectAtIndex:1]; + + // We need to decode the parameters here because most URL + // parsers treat the query component as needing to be decoded + // instead of treating it as is. It does mean that a link to + // open file "/tmp/file name.txt" will be + // mvim://open?url=file:///tmp/file%2520name.txt to encode a + // URL of file:///tmp/file%20name.txt. This double-encoding is + // intentional to follow the URL spec. +#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 + v = [v stringByRemovingPercentEncoding]; +#else + v = [v stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; +#endif + + if ([f isEqualToString:@"url"]) { + // Since the URL scheme uses a double-encoding due to a + // file:// URL encoded in another mvim: one, existing tools + // like iTerm2 could sometimes erroneously only do a single + // encode. To maximize compatiblity, we re-encode invalid + // characters if we detect them as they would not work + // later on when we pass this string to URLWithString. + // + // E.g. mvim://open?uri=file:///foo%20bar => "file:///foo bar" + // which is not a valid URL, so we re-encode it to + // file:///foo%20bar here. The only important case is to + // not touch the "%" character as it introduces ambiguity + // and the re-encoding is a nice compatibility feature, but + // the canonical form should be double-encoded, i.e. + // mvim://open?uri=file:///foo%2520bar +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_10 + if (AVAILABLE_MAC_OS(10, 10)) { + NSMutableCharacterSet *charSet = [NSMutableCharacterSet characterSetWithCharactersInString:@"%"]; + [charSet formUnionWithCharacterSet:NSCharacterSet.URLHostAllowedCharacterSet]; + [charSet formUnionWithCharacterSet:NSCharacterSet.URLPathAllowedCharacterSet]; + v = [v stringByAddingPercentEncodingWithAllowedCharacters:charSet]; + } +#endif + } + + [dict setValue:v forKey:f]; + } + } + return dict; +} + - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event replyEvent:(NSAppleEventDescriptor *)reply { @@ -2138,7 +2183,7 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event stringValue]]; // We try to be compatible with TextMate's URL scheme here, as documented - // at http://blog.macromates.com/2007/the-textmate-url-scheme/ . Currently, + // at https://macromates.com/blog/2007/the-textmate-url-scheme/ . Currently, // this means that: // // The format is: mvim://open? where arguments can be: @@ -2151,66 +2196,8 @@ - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event // Example: mvim://open?url=file:///etc/profile&line=20 if ([[url host] isEqualToString:@"open"]) { - NSMutableDictionary *dict = [NSMutableDictionary dictionary]; - - // Parse query ("url=file://...&line=14") into a dictionary - NSArray *queries = [[url query] componentsSeparatedByString:@"&"]; - NSEnumerator *enumerator = [queries objectEnumerator]; - NSString *param; - while ((param = [enumerator nextObject])) { - // query: = - NSArray *arr = [param componentsSeparatedByString:@"="]; - if ([arr count] == 2) { - // parse field - NSString *f = [arr objectAtIndex:0]; -#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 - f = [f stringByRemovingPercentEncoding]; -#else - f = [f stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; -#endif - - // parse value - NSString *v = [arr objectAtIndex:1]; - - // We need to decode the parameters here because most URL - // parsers treat the query component as needing to be decoded - // instead of treating it as is. It does mean that a link to - // open file "/tmp/file name.txt" will be - // mvim://open?url=file:///tmp/file%2520name.txt to encode a - // URL of file:///tmp/file%20name.txt. This double-encoding is - // intentional to follow the URL spec. -#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 - v = [v stringByRemovingPercentEncoding]; -#else - v = [v stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; -#endif - - if ([f isEqualToString:@"url"]) { - // Since the URL scheme uses a double-encoding due to a - // file:// URL encoded in another mvim: one, existing tools - // like iTerm2 could sometimes erroneously only do a single - // encode. To maximize compatiblity, we re-encode invalid - // characters if we detect them as they would not work - // later on when we pass this string to URLWithString. - // - // E.g. mvim://open?uri=file:///foo%20bar => "file:///foo bar" - // which is not a valid URL, so we re-encode it to - // file:///foo%20bar here. The only important case is to - // not touch the "%" character as it introduces ambiguity - // and the re-encoding is a nice compatibility feature, but - // the canonical form should be double-encoded, i.e. - // mvim://open?uri=file:///foo%2520bar -#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11 - NSMutableCharacterSet *charSet = [NSMutableCharacterSet characterSetWithCharactersInString:@"%"]; - [charSet formUnionWithCharacterSet:NSCharacterSet.URLHostAllowedCharacterSet]; - [charSet formUnionWithCharacterSet:NSCharacterSet.URLPathAllowedCharacterSet]; - v = [v stringByAddingPercentEncodingWithAllowedCharacters:charSet]; -#endif - } - - [dict setValue:v forKey:f]; - } - } + // Parse the URL and process it + NSDictionary *dict = [MMAppController parseOpenURL:url]; // Actually open the file. NSString *file = [dict objectForKey:@"url"]; diff --git a/src/MacVim/MMBackend.m b/src/MacVim/MMBackend.m index 5e3bc0ff45..cd127be61e 100644 --- a/src/MacVim/MMBackend.m +++ b/src/MacVim/MMBackend.m @@ -2319,6 +2319,10 @@ - (void)handleInputEvent:(int)msgid data:(NSData *)data [self setImState:NO]; } else if (BackingPropertiesChangedMsgID == msgid) { [self redrawScreen]; + } else if (LoopBackMsgID == msgid) { + // This is a debug message used for confirming a message has been + // received and echoed back to caller for synchronization purpose. + [self queueMessage:msgid data:nil]; } else { ASLogWarn(@"Unknown message received (msgid=%d)", msgid); } diff --git a/src/MacVim/MacVim.h b/src/MacVim/MacVim.h index 946e729c45..97d662636a 100644 --- a/src/MacVim/MacVim.h +++ b/src/MacVim/MacVim.h @@ -344,6 +344,7 @@ extern const char * const MMVimMsgIDStrings[]; MSG(EnableThinStrokesMsgID) \ MSG(DisableThinStrokesMsgID) \ MSG(ShowDefinitionMsgID) \ + MSG(LoopBackMsgID) /* Simple message that Vim will reflect back to MacVim */ \ MSG(LastMsgID) \ enum { diff --git a/src/MacVim/MacVim.xcodeproj/project.pbxproj b/src/MacVim/MacVim.xcodeproj/project.pbxproj index f8e239e526..a10e62d398 100644 --- a/src/MacVim/MacVim.xcodeproj/project.pbxproj +++ b/src/MacVim/MacVim.xcodeproj/project.pbxproj @@ -71,6 +71,7 @@ 907FF7512521BCE200BADACB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 907FF74F2521BCE200BADACB /* MainMenu.xib */; }; 907FF7542521BDA600BADACB /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 907FF7522521BDA600BADACB /* Preferences.xib */; }; 907FF7572521BDC300BADACB /* FindAndReplace.xib in Resources */ = {isa = PBXBuildFile; fileRef = 907FF7552521BDC200BADACB /* FindAndReplace.xib */; }; + 908A1E092AE496D200AB5862 /* MacVimTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 908A1E082AE496D200AB5862 /* MacVimTests.m */; }; 909894382A56EB1E007B84A3 /* WhatsNew.xib in Resources */ = {isa = PBXBuildFile; fileRef = 909894362A56EB1E007B84A3 /* WhatsNew.xib */; }; 9098943C2A56ECF6007B84A3 /* MMWhatsNewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9098943B2A56ECF6007B84A3 /* MMWhatsNewController.m */; }; 90A33BEA28D563DF003A2E2F /* MMSparkle2Delegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 90A33BE928D563DF003A2E2F /* MMSparkle2Delegate.m */; }; @@ -107,6 +108,13 @@ remoteGlobalIDString = 8D57630D048677EA00EA77CD; remoteInfo = QuickLookStephen; }; + 908A1E0A2AE496D200AB5862 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D1107260486CEB800E47090; + remoteInfo = MacVim; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -295,6 +303,9 @@ 907FF7622521C2FB00BADACB /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = ""; }; 907FF7632521CBAC00BADACB /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = ""; }; 907FF7642521CBC500BADACB /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/MainMenu.strings; sourceTree = ""; }; + 908A1E002AE4965900AB5862 /* MacVim.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MacVim.xctestplan; sourceTree = ""; }; + 908A1E062AE496D200AB5862 /* MacVimTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MacVimTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 908A1E082AE496D200AB5862 /* MacVimTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MacVimTests.m; sourceTree = ""; }; 90922A3B221D429500F1E1F4 /* misc2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = misc2.c; path = ../misc2.c; sourceTree = ""; }; 90922A3C221D429500F1E1F4 /* if_mzsch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = if_mzsch.c; path = ../if_mzsch.c; sourceTree = ""; }; 90922A3D221D429500F1E1F4 /* version.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = version.h; path = ../version.h; sourceTree = ""; }; @@ -418,13 +429,13 @@ 9098943B2A56ECF6007B84A3 /* MMWhatsNewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMWhatsNewController.m; sourceTree = ""; }; 90A33BE928D563DF003A2E2F /* MMSparkle2Delegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MMSparkle2Delegate.m; sourceTree = ""; }; 90A33BEC28D56423003A2E2F /* MMSparkle2Delegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MMSparkle2Delegate.h; sourceTree = ""; }; + 90AF83A92A8C37F70046DA2E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 90AF83B32AA15BE50046DA2E /* if_perl.xs */ = {isa = PBXFileReference; explicitFileType = sourcecode.c; name = if_perl.xs; path = ../if_perl.xs; sourceTree = ""; }; 90AF83B42AA15C660046DA2E /* nv_cmdidxs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = nv_cmdidxs.h; path = ../nv_cmdidxs.h; sourceTree = ""; }; 90AF83B52AA15C660046DA2E /* alloc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = alloc.c; path = ../alloc.c; sourceTree = ""; }; 90AF83B62AA15C660046DA2E /* nv_cmds.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = nv_cmds.h; path = ../nv_cmds.h; sourceTree = ""; }; 90AF83B72AA15C660046DA2E /* vim9cmds.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = vim9cmds.c; path = ../vim9cmds.c; sourceTree = ""; }; 90AF83B82AA15C660046DA2E /* termdefs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = termdefs.h; path = ../termdefs.h; sourceTree = ""; }; - 90AF83A92A8C37F70046DA2E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 90B9877B2A579F9500FC95D6 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 90F84F1E2521F2270000268B /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/MainMenu.strings; sourceTree = ""; }; 90F84F232521F6480000268B /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/MainMenu.strings; sourceTree = ""; }; @@ -473,6 +484,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 908A1E032AE496D200AB5862 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -545,6 +563,7 @@ isa = PBXGroup; children = ( 8D1107320486CEB800E47090 /* MacVim.app */, + 908A1E062AE496D200AB5862 /* MacVimTests.xctest */, ); name = Products; sourceTree = ""; @@ -613,11 +632,13 @@ 29B97314FDCFA39411CA2CEA /* MacVim */ = { isa = PBXGroup; children = ( + 908A1E002AE4965900AB5862 /* MacVim.xctestplan */, 1D493D640C52482B00AB718C /* Executables */, 080E96DDFE201D6D7F000001 /* MacVim Source */, 29B97317FDCFA39411CA2CEA /* Resources */, 1DE602460C587F760055263D /* Vim Resources */, 90922A3A221D417800F1E1F4 /* Vim Source */, + 908A1E072AE496D200AB5862 /* MacVimTests */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 52818AF81C1C073400F59085 /* QuickLook Plugin */, 19C28FACFE9D520D11CA2CBB /* Products */, @@ -680,6 +701,14 @@ name = Products; sourceTree = ""; }; + 908A1E072AE496D200AB5862 /* MacVimTests */ = { + isa = PBXGroup; + children = ( + 908A1E082AE496D200AB5862 /* MacVimTests.m */, + ); + path = MacVimTests; + sourceTree = ""; + }; 90922A3A221D417800F1E1F4 /* Vim Source */ = { isa = PBXGroup; children = ( @@ -903,6 +932,24 @@ productReference = 8D1107320486CEB800E47090 /* MacVim.app */; productType = "com.apple.product-type.application"; }; + 908A1E052AE496D200AB5862 /* MacVimTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 908A1E0C2AE496D200AB5862 /* Build configuration list for PBXNativeTarget "MacVimTests" */; + buildPhases = ( + 908A1E022AE496D200AB5862 /* Sources */, + 908A1E032AE496D200AB5862 /* Frameworks */, + 908A1E042AE496D200AB5862 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 908A1E0B2AE496D200AB5862 /* PBXTargetDependency */, + ); + name = MacVimTests; + productName = MacVimTests; + productReference = 908A1E062AE496D200AB5862 /* MacVimTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -911,6 +958,12 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 0710; + TargetAttributes = { + 908A1E052AE496D200AB5862 = { + CreatedOnToolsVersion = 15.0.1; + TestTargetID = 8D1107260486CEB800E47090; + }; + }; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "MacVim" */; compatibilityVersion = "Xcode 6.3"; @@ -956,6 +1009,7 @@ projectRoot = ""; targets = ( 8D1107260486CEB800E47090 /* MacVim */, + 908A1E052AE496D200AB5862 /* MacVimTests */, ); }; /* End PBXProject section */ @@ -1020,6 +1074,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 908A1E042AE496D200AB5862 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -1176,6 +1237,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 908A1E022AE496D200AB5862 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 908A1E092AE496D200AB5862 /* MacVimTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1189,6 +1258,11 @@ name = QuickLookStephen; targetProxy = 52818B001C1C084100F59085 /* PBXContainerItemProxy */; }; + 908A1E0B2AE496D200AB5862 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8D1107260486CEB800E47090 /* MacVim */; + targetProxy = 908A1E0A2AE496D200AB5862 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1256,6 +1330,123 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 908A1E0D2AE496D200AB5862 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GENERATE_INFOPLIST_FILE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MacVim.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MacVim"; + }; + name = Debug; + }; + 908A1E0E2AE496D200AB5862 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GENERATE_INFOPLIST_FILE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MacVim.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MacVim"; + }; + name = Release; + }; C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1352,6 +1543,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 908A1E0C2AE496D200AB5862 /* Build configuration list for PBXNativeTarget "MacVimTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 908A1E0D2AE496D200AB5862 /* Debug */, + 908A1E0E2AE496D200AB5862 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "MacVim" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/src/MacVim/MacVim.xcodeproj/xcshareddata/xcschemes/MacVim.xcscheme b/src/MacVim/MacVim.xcodeproj/xcshareddata/xcschemes/MacVim.xcscheme new file mode 100644 index 0000000000..475716d852 --- /dev/null +++ b/src/MacVim/MacVim.xcodeproj/xcshareddata/xcschemes/MacVim.xcscheme @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MacVim/MacVim.xctestplan b/src/MacVim/MacVim.xctestplan new file mode 100644 index 0000000000..31c2a80b81 --- /dev/null +++ b/src/MacVim/MacVim.xctestplan @@ -0,0 +1,32 @@ +{ + "configurations" : [ + { + "id" : "6F3712E8-FA03-4BC3-99F0-F08F08C9C4F4", + "name" : "Configuration 1", + "options" : { + + } + } + ], + "defaultOptions" : { + "commandLineArgumentEntries" : [ + { + "argument" : "-IgnoreUserDefaults 1" + }, + { + "argument" : "-MMUntitledWindow 0" + } + ], + "testTimeoutsEnabled" : true + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:MacVim.xcodeproj", + "identifier" : "908A1E052AE496D200AB5862", + "name" : "MacVimTests" + } + } + ], + "version" : 1 +} diff --git a/src/MacVim/MacVimTests/MacVimTests.m b/src/MacVim/MacVimTests/MacVimTests.m new file mode 100644 index 0000000000..05a191349c --- /dev/null +++ b/src/MacVim/MacVimTests/MacVimTests.m @@ -0,0 +1,334 @@ +// +// MacVimTests.m +// +// Contains unit tests and end-to-end app tests. Currently everything is in one +// file as we only have a few tests. As we expand test coverage we should split +// them up and refactor to more logical components. +// + +#import + +#import + +#import "Miscellaneous.h" +#import "MMAppController.h" +#import "MMApplication.h" +#import "MMTextView.h" +#import "MMWindowController.h" +#import "MMVimController.h" +#import "MMVimView.h" + +// Expose private methods for testing purposes +@interface MMAppController (Private) ++ (NSDictionary*)parseOpenURL:(NSURL*)url; +@end + +@interface MMVimController (Private) +- (void)handleMessage:(int)msgid data:(NSData *)data; +@end + +// Test harness +@interface MMAppController (Tests) +- (NSMutableArray*)vimControllers; +@end + +@implementation MMAppController (Tests) +- (NSMutableArray*)vimControllers { + return vimControllers; +} +@end + +@interface MacVimTests : XCTestCase + +@end + +@implementation MacVimTests + +/// Wait for a Vim controller to be added/removed. By the time this is fulfilled +/// the Vim window should be ready and visible. +- (void)waitForVimController:(int)delta { + NSArray *vimControllers = [MMAppController.sharedInstance vimControllers]; + const int desiredCount = (int)vimControllers.count + delta; + [self waitForExpectations:@[[[XCTNSPredicateExpectation alloc] + initWithPredicate:[NSPredicate predicateWithBlock:^(id vimControllers, NSDictionary *bindings) { + return (BOOL)((int)[(NSArray*)vimControllers count] == desiredCount); + }] + object:vimControllers]] + timeout:5]; +} + +/// Wait for event handling to be finished at the main loop. +- (void)waitForEventHandling { + // Inject a custom event. By the time we handle this event all queued events + // will have been consumed. + const NSInteger appEventType = 1687648131; // magic number to prevent collisions + XCTestExpectation *expectation = [self expectationWithDescription:@"EventHandling"]; + + SEL sel = @selector(sendEvent:); + Method method = class_getInstanceMethod([MMApplication class], sel); + + IMP origIMP = method_getImplementation(method); + IMP newIMP = imp_implementationWithBlock(^(id self, NSEvent *event) { + typedef void (*fn)(id,SEL,NSEvent*); + if (event.type == NSEventTypeApplicationDefined && event.data1 == appEventType) { + [expectation fulfill]; + } else { + ((fn)origIMP)(self, sel, event); + } + }); + + NSApplication* app = [NSApplication sharedApplication]; + NSEvent* customEvent = [NSEvent otherEventWithType:NSEventTypeApplicationDefined + location:NSMakePoint(50, 50) + modifierFlags:0 + timestamp:100 + windowNumber:[[NSApp mainWindow] windowNumber] + context:0 + subtype:0 + data1:appEventType + data2:0]; + + method_setImplementation(method, newIMP); + + [app postEvent:customEvent atStart:NO]; + [self waitForExpectations:@[expectation] timeout:10]; + + method_setImplementation(method, origIMP); +} + +/// Wait for Vim to process all pending messages in its queue. +- (void)waitForVimProcess { + // Implement this by sending a loopback message (Vim will send the message + // back to us) as a synchronization mechanism as Vim handles its messages + // sequentially. + XCTestExpectation *expectation = [self expectationWithDescription:@"VimLoopBack"]; + + SEL sel = @selector(handleMessage:data:); + Method method = class_getInstanceMethod([MMVimController class], sel); + + IMP origIMP = method_getImplementation(method); + IMP newIMP = imp_implementationWithBlock(^(id self, int msgid, NSData *data) { + typedef void (*fn)(id,SEL,int,NSData*); + if (msgid == LoopBackMsgID) { + [expectation fulfill]; + } else { + ((fn)origIMP)(self, sel, msgid, data); + } + }); + + method_setImplementation(method, newIMP); + + [[MMAppController.sharedInstance keyVimController] sendMessage:LoopBackMsgID data:nil]; + [self waitForExpectations:@[expectation] timeout:10]; + + method_setImplementation(method, origIMP); +} + +/// Wait for both event handling to be finished at the main loop and for Vim to +/// process all pending messages in its queue. +- (void)waitForEventHandlingAndVimProcess { + [self waitForEventHandling]; + [self waitForVimProcess]; +} + +/// Wait for a fixed timeout before fulfilling expectation. +/// +/// @note Should only be used for quick iteration / debugging unless we cannot +/// find an alternative way to specify an expectation, as timeouts tend to be +/// fragile and take more time to complete. +- (void)waitTimeout:(double)delaySecs { + XCTestExpectation *expectation = [self expectationWithDescription:@"Timeout"]; + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delaySecs * NSEC_PER_SEC)); + dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ + [expectation fulfill]; + }); + [self waitForExpectations:@[expectation] timeout:delaySecs + 10]; +} + +/// Send a single key to MacVim via event handling system. +- (void)sendKeyToVim:(NSString*)chars withMods:(int)mods { + NSApplication* app = [NSApplication sharedApplication]; + NSEvent* keyEvent = [NSEvent keyEventWithType:NSEventTypeKeyDown + location:NSMakePoint(50, 50) + modifierFlags:mods + timestamp:100 + windowNumber:[[NSApp mainWindow] windowNumber] + context:0 + characters:chars + charactersIgnoringModifiers:chars + isARepeat:NO + keyCode:0]; + + [app postEvent:keyEvent atStart:NO]; +} + +/// Send a string to MacVim via event handling system. Each character will be +/// sent separately as if the user typed it. +- (void)sendStringToVim:(NSString*)chars withMods:(int)mods { + for (NSUInteger i = 0; i < chars.length; i++) { + unichar ch = [chars characterAtIndex:i]; + NSString *str = [NSString stringWithCharacters:&ch length:1]; + [self sendKeyToVim:str withMods:mods]; + } +} + +- (void)testCompareSemanticVersions { + // bogus values evaluate to 0 + XCTAssertEqual(0, compareSemanticVersions(@"bogus", @"")); + XCTAssertEqual(0, compareSemanticVersions(@"bogus", @"0")); + XCTAssertEqual(0, compareSemanticVersions(@"", @"")); + + // single values + XCTAssertEqual(1, compareSemanticVersions(@"", @"1")); + XCTAssertEqual(-1, compareSemanticVersions(@"1", @"")); + XCTAssertEqual(1, compareSemanticVersions(@"100", @"101")); + XCTAssertEqual(-1, compareSemanticVersions(@"101", @"100")); + + // multiple semantic values + XCTAssertEqual(1, compareSemanticVersions(@"100", @"100.1")); + XCTAssertEqual(-1, compareSemanticVersions(@"100.1", @"100")); + XCTAssertEqual(1, compareSemanticVersions(@"100.2", @"100.3")); + XCTAssertEqual(-1, compareSemanticVersions(@"100.10", @"100.2")); // double digit after the dot to make sure we are parsing it properly + XCTAssertEqual(0, compareSemanticVersions(@"234.5", @"234.5")); + XCTAssertEqual(-1, compareSemanticVersions(@"234.5.1", @"234.5")); + XCTAssertEqual(1, compareSemanticVersions(@"234.5", @"234.5.0")); +} + +/// Tests that parseOpenURL complies with the spec. See ":h macvim-url-handler". +- (void)testParseOpenURL { + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?"]], @{}); + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?url=file:///foo/bar"]], @{@"url": @"file:///foo/bar"}); + + // Test that we correctly decode the URL, where special characters like space need to be double encoded. + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?url=file:///foo/bar%2520file"]], @{@"url": @"file:///foo/bar%20file"}); + XCTAssertEqualObjects([[NSURL URLWithString:@"file:///foo/bar%20file"] path], @"/foo/bar file"); + // Test opportunistic single-encoding for compatibility with old behaviors and other tools. + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?url=file:///foo/bar%20file"]], @{@"url": @"file:///foo/bar%20file"}); + + // Test mixed single/double-encoding. + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?url=file:///foo/bar%20%2520file%253F"]], @{@"url": @"file:///foo/bar%20%20file%3F"}); + + // Test that with certain special characters like "&", you have to encode at least once, as otherwise it will be interpreted as a separator. + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?url=file:///foo&bar"]], @{@"url": @"file:///foo"}); // lost the "bar" in the path + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?url=file:///foo%26bar"]], @{@"url": @"file:///foo&bar"}); + XCTAssertEqualObjects([[NSURL URLWithString:@"file:///foo&bar"] path], @"/foo&bar"); + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?url=file:///foo%2526bar"]], @{@"url": @"file:///foo%26bar"}); + XCTAssertEqualObjects([[NSURL URLWithString:@"file:///foo%26bar"] path], @"/foo&bar"); + + // Test that '%' in a file name is a special case, where only double-encoding works. The opportunistic single-encoding doesn't work here. + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?url=file:///foo%bar"]], @{}); // This should fail at decoding step + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?url=file:///foo%25bar"]], @{@"url": @"file:///foo%bar"}); // Not valid file URL + XCTAssertEqualObjects([[NSURL URLWithString:@"file:///foo%bar"] path], nil); // Invalid decoded file URL leads to nil + XCTAssertEqualObjects([MMAppController parseOpenURL:[NSURL URLWithString:@"mvim://open?url=file:///foo%2525bar"]], @{@"url": @"file:///foo%25bar"}); + XCTAssertEqualObjects([[NSURL URLWithString:@"file:///foo%25bar"] path], @"/foo%bar"); +} + +/// Test that the "Vim Tutor" menu item works and can be used to launch the +/// bundled vimtutor. Previously this was silently broken by Vim v8.2.3502 +/// and fixed in https://github.com/macvim-dev/macvim/pull/1265. +- (void)testVimTutor { + MMAppController *app = MMAppController.sharedInstance; + + // Adding a new window is necessary for the vimtutor menu to show up as it's + // not part of the global menu + [app openNewWindow:NewWindowClean activate:YES]; + [self waitForVimController:1]; + + // Find the vimtutor menu and run it. + NSMenu *mainMenu = [NSApp mainMenu]; + NSMenu *helpMenu = [mainMenu findHelpMenu]; + NSMenuItem *vimTutorMenu = nil; + for (NSInteger i = 0; i < helpMenu.numberOfItems; ++i) { + NSMenuItem *menuItem = [helpMenu itemAtIndex:i]; + if ([menuItem.title isEqualToString:@"Vim Tutor"]) + vimTutorMenu = menuItem; + } + XCTAssertNotNil(vimTutorMenu); + XCTAssertEqual(vimTutorMenu.action, @selector(vimMenuItemAction:)); + [[[app keyVimController] windowController] vimMenuItemAction:vimTutorMenu]; + + // Make sure the menu item actually opened a new window and point to a tutor buffer + [self waitForVimController:1]; + + NSString *bufname = [[app keyVimController] evaluateVimExpression:@"bufname()"]; + XCTAssertTrue([bufname containsString:@"tutor"]); + + // Clean up + [[app keyVimController] sendMessage:VimShouldCloseMsgID data:nil]; + [self waitForVimController:-1]; + [[app keyVimController] sendMessage:VimShouldCloseMsgID data:nil]; + [self waitForVimController:-1]; +} + +/// Test that cmdline row calculation (used by MMCmdLineAlignBottom) is correct. +/// This is an integration test as the calculation is done in Vim, which has +/// special logic to account for "Press Enter" and "--more--" prompts when showing +/// messages. +- (void) testCmdlineRowCalculation { + MMAppController *app = MMAppController.sharedInstance; + + [app openNewWindow:NewWindowClean activate:YES]; + [self waitForVimController:1]; + + MMTextView *textView = [[[[app keyVimController] windowController] vimView] textView]; + const int numLines = [textView maxRows]; + const int numCols = [textView maxColumns]; + + // Define convenience macro (don't use functions to preserve line numbers in callstack) +#define ASSERT_NUM_CMDLINES(expected) \ +{ \ + const int cmdlineRow = [[[app keyVimController] objectForVimStateKey:@"cmdline_row"] intValue]; \ + const int numBottomLines = numLines - cmdlineRow; \ + XCTAssertEqual(expected, numBottomLines); \ +} + + // Default value + [self waitForEventHandlingAndVimProcess]; + ASSERT_NUM_CMDLINES(1); + + // Print more lines than we have room for to trigger "Press Enter" + [self sendStringToVim:@":echo join(repeat(['test line'], 3), \"\\n\")\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + ASSERT_NUM_CMDLINES(1); + + // Test non-1 cmdheight works + [self sendStringToVim:@":set cmdheight=3\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + ASSERT_NUM_CMDLINES(3); + + // Test typing enough characters to cause cmdheight to grow + [self sendStringToVim:[@":\"" stringByPaddingToLength:numCols * 3 - 1 withString:@"a" startingAtIndex:0] withMods:0]; + [self waitForEventHandlingAndVimProcess]; + ASSERT_NUM_CMDLINES(3); + + [self sendStringToVim:@"bbbb" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + ASSERT_NUM_CMDLINES(4); + + [self sendStringToVim:@"\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + ASSERT_NUM_CMDLINES(3); + + // Printing just enough lines within cmdheight should not affect anything + [self sendStringToVim:@":echo join(repeat(['test line'], 3), \"\\n\")\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + ASSERT_NUM_CMDLINES(3); + + // Printing more lines than cmdheight will once again trigger "Press Enter" + [self sendStringToVim:@":echo join(repeat(['test line'], 4), \"\\n\")\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + ASSERT_NUM_CMDLINES(1); + + // Printing more lines than the screen will trigger "--more--" prompt + [self sendStringToVim:@":echo join(repeat(['test line'], 2000), \"\\n\")\n" withMods:0]; + [self waitForEventHandlingAndVimProcess]; + ASSERT_NUM_CMDLINES(1); + +#undef ASSERT_NUM_CMDLINES + + // Clean up + [[app keyVimController] sendMessage:VimShouldCloseMsgID data:nil]; + [self waitForVimController:-1]; +} + +@end diff --git a/src/MacVim/MacVim_xcode8.xcodeproj/project.pbxproj b/src/MacVim/MacVim_xcode8.xcodeproj/project.pbxproj index b1c8c0612b..070f45448b 100644 --- a/src/MacVim/MacVim_xcode8.xcodeproj/project.pbxproj +++ b/src/MacVim/MacVim_xcode8.xcodeproj/project.pbxproj @@ -72,6 +72,7 @@ 907FF7512521BCE200BADACB /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 907FF74F2521BCE200BADACB /* MainMenu.xib */; }; 907FF7542521BDA600BADACB /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 907FF7522521BDA600BADACB /* Preferences.xib */; }; 907FF7572521BDC300BADACB /* FindAndReplace.xib in Resources */ = {isa = PBXBuildFile; fileRef = 907FF7552521BDC200BADACB /* FindAndReplace.xib */; }; + 908A1E092AE496D200AB5862 /* MacVimTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 908A1E082AE496D200AB5862 /* MacVimTests.m */; }; 909894382A56EB1E007B84A3 /* WhatsNew.xib in Resources */ = {isa = PBXBuildFile; fileRef = 909894362A56EB1E007B84A3 /* WhatsNew.xib */; }; 9098943C2A56ECF6007B84A3 /* MMWhatsNewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9098943B2A56ECF6007B84A3 /* MMWhatsNewController.m */; }; 90A33BEA28D563DF003A2E2F /* MMSparkle2Delegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 90A33BE928D563DF003A2E2F /* MMSparkle2Delegate.m */; }; @@ -108,6 +109,13 @@ remoteGlobalIDString = 8D57630D048677EA00EA77CD; remoteInfo = QuickLookStephen; }; + 908A1E0A2AE496D200AB5862 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 29B97313FDCFA39411CA2CEA /* Project object */; + proxyType = 1; + remoteGlobalIDString = 8D1107260486CEB800E47090; + remoteInfo = MacVim; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -296,6 +304,9 @@ 907FF7622521C2FB00BADACB /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/MainMenu.strings"; sourceTree = ""; }; 907FF7632521CBAC00BADACB /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = ""; }; 907FF7642521CBC500BADACB /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/MainMenu.strings; sourceTree = ""; }; + 908A1E002AE4965900AB5862 /* MacVim.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MacVim.xctestplan; sourceTree = ""; }; + 908A1E062AE496D200AB5862 /* MacVimTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MacVimTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 908A1E082AE496D200AB5862 /* MacVimTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MacVimTests.m; sourceTree = ""; }; 90922A3B221D429500F1E1F4 /* misc2.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = misc2.c; path = ../misc2.c; sourceTree = ""; }; 90922A3C221D429500F1E1F4 /* if_mzsch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = if_mzsch.c; path = ../if_mzsch.c; sourceTree = ""; }; 90922A3D221D429500F1E1F4 /* version.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = version.h; path = ../version.h; sourceTree = ""; }; @@ -419,13 +430,13 @@ 9098943B2A56ECF6007B84A3 /* MMWhatsNewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMWhatsNewController.m; sourceTree = ""; }; 90A33BE928D563DF003A2E2F /* MMSparkle2Delegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MMSparkle2Delegate.m; sourceTree = ""; }; 90A33BEC28D56423003A2E2F /* MMSparkle2Delegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MMSparkle2Delegate.h; sourceTree = ""; }; + 90AF83A92A8C37F70046DA2E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 90AF83B32AA15BE50046DA2E /* if_perl.xs */ = {isa = PBXFileReference; explicitFileType = sourcecode.c; name = if_perl.xs; path = ../if_perl.xs; sourceTree = ""; }; 90AF83B42AA15C660046DA2E /* nv_cmdidxs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = nv_cmdidxs.h; path = ../nv_cmdidxs.h; sourceTree = ""; }; 90AF83B52AA15C660046DA2E /* alloc.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = alloc.c; path = ../alloc.c; sourceTree = ""; }; 90AF83B62AA15C660046DA2E /* nv_cmds.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = nv_cmds.h; path = ../nv_cmds.h; sourceTree = ""; }; 90AF83B72AA15C660046DA2E /* vim9cmds.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = vim9cmds.c; path = ../vim9cmds.c; sourceTree = ""; }; 90AF83B82AA15C660046DA2E /* termdefs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = termdefs.h; path = ../termdefs.h; sourceTree = ""; }; - 90AF83A92A8C37F70046DA2E /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 90B9877B2A579F9500FC95D6 /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 90F84F1E2521F2270000268B /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/MainMenu.strings; sourceTree = ""; }; 90F84F232521F6480000268B /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/MainMenu.strings; sourceTree = ""; }; @@ -474,6 +485,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 908A1E032AE496D200AB5862 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -546,6 +564,7 @@ isa = PBXGroup; children = ( 8D1107320486CEB800E47090 /* MacVim.app */, + 908A1E062AE496D200AB5862 /* MacVimTests.xctest */, ); name = Products; sourceTree = ""; @@ -614,11 +633,13 @@ 29B97314FDCFA39411CA2CEA /* MacVim */ = { isa = PBXGroup; children = ( + 908A1E002AE4965900AB5862 /* MacVim.xctestplan */, 1D493D640C52482B00AB718C /* Executables */, 080E96DDFE201D6D7F000001 /* MacVim Source */, 29B97317FDCFA39411CA2CEA /* Resources */, 1DE602460C587F760055263D /* Vim Resources */, 90922A3A221D417800F1E1F4 /* Vim Source */, + 908A1E072AE496D200AB5862 /* MacVimTests */, 29B97323FDCFA39411CA2CEA /* Frameworks */, 52818AF81C1C073400F59085 /* QuickLook Plugin */, 19C28FACFE9D520D11CA2CBB /* Products */, @@ -681,6 +702,14 @@ name = Products; sourceTree = ""; }; + 908A1E072AE496D200AB5862 /* MacVimTests */ = { + isa = PBXGroup; + children = ( + 908A1E082AE496D200AB5862 /* MacVimTests.m */, + ); + path = MacVimTests; + sourceTree = ""; + }; 90922A3A221D417800F1E1F4 /* Vim Source */ = { isa = PBXGroup; children = ( @@ -904,6 +933,24 @@ productReference = 8D1107320486CEB800E47090 /* MacVim.app */; productType = "com.apple.product-type.application"; }; + 908A1E052AE496D200AB5862 /* MacVimTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 908A1E0C2AE496D200AB5862 /* Build configuration list for PBXNativeTarget "MacVimTests" */; + buildPhases = ( + 908A1E022AE496D200AB5862 /* Sources */, + 908A1E032AE496D200AB5862 /* Frameworks */, + 908A1E042AE496D200AB5862 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 908A1E0B2AE496D200AB5862 /* PBXTargetDependency */, + ); + name = MacVimTests; + productName = MacVimTests; + productReference = 908A1E062AE496D200AB5862 /* MacVimTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -912,6 +959,12 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 0710; + TargetAttributes = { + 908A1E052AE496D200AB5862 = { + CreatedOnToolsVersion = 15.0.1; + TestTargetID = 8D1107260486CEB800E47090; + }; + }; }; buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "MacVim" */; compatibilityVersion = "Xcode 6.3"; @@ -957,6 +1010,7 @@ projectRoot = ""; targets = ( 8D1107260486CEB800E47090 /* MacVim */, + 908A1E052AE496D200AB5862 /* MacVimTests */, ); }; /* End PBXProject section */ @@ -1021,6 +1075,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 908A1E042AE496D200AB5862 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -1177,6 +1238,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 908A1E022AE496D200AB5862 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 908A1E092AE496D200AB5862 /* MacVimTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -1190,6 +1259,11 @@ name = QuickLookStephen; targetProxy = 52818B001C1C084100F59085 /* PBXContainerItemProxy */; }; + 908A1E0B2AE496D200AB5862 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 8D1107260486CEB800E47090 /* MacVim */; + targetProxy = 908A1E0A2AE496D200AB5862 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -1257,6 +1331,123 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 908A1E0D2AE496D200AB5862 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GENERATE_INFOPLIST_FILE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MacVim.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MacVim"; + }; + name = Debug; + }; + 908A1E0E2AE496D200AB5862 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GENERATE_INFOPLIST_FILE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MacVim.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MacVim"; + }; + name = Release; + }; C01FCF4B08A954540054247B /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1353,6 +1544,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 908A1E0C2AE496D200AB5862 /* Build configuration list for PBXNativeTarget "MacVimTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 908A1E0D2AE496D200AB5862 /* Debug */, + 908A1E0E2AE496D200AB5862 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "MacVim" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/src/MacVim/Miscellaneous.h b/src/MacVim/Miscellaneous.h index e8b1607232..d245fbfa32 100644 --- a/src/MacVim/Miscellaneous.h +++ b/src/MacVim/Miscellaneous.h @@ -162,7 +162,7 @@ enum { // Create a view with a "show hidden files" button to be used as accessory for // open/save panels. This function assumes ownership of the view so do not // release it. -NSView *showHiddenFilesView(); +NSView *showHiddenFilesView(void); // Convert filenames (which are in a variant of decomposed form, NFD, on HFS+) @@ -175,11 +175,13 @@ NSView *showHiddenFilesView(); NSString *normalizeFilename(NSString *filename); NSArray *normalizeFilenames(NSArray *filenames); -BOOL shouldUseYosemiteTabBarStyle(); -BOOL shouldUseMojaveTabBarStyle(); +BOOL shouldUseYosemiteTabBarStyle(void); +BOOL shouldUseMojaveTabBarStyle(void); int getCurrentAppearance(NSAppearance *appearance); // Pasteboard helpers -NSPasteboardType getPasteboardFilenamesType(); +NSPasteboardType getPasteboardFilenamesType(void); NSArray* extractPasteboardFilenames(NSPasteboard *pboard); + +int compareSemanticVersions(NSString *oldVersion, NSString *newVersion); diff --git a/src/MacVim/Miscellaneous.m b/src/MacVim/Miscellaneous.m index acc5e0ab21..50d5fd261d 100644 --- a/src/MacVim/Miscellaneous.m +++ b/src/MacVim/Miscellaneous.m @@ -263,7 +263,7 @@ - (NSInteger)tag NSView * -showHiddenFilesView() +showHiddenFilesView(void) { // Return a new button object for each NSOpenPanel -- several of them // could be displayed at once. @@ -320,12 +320,12 @@ - (NSInteger)tag BOOL -shouldUseYosemiteTabBarStyle() +shouldUseYosemiteTabBarStyle(void) { return floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_10; } BOOL -shouldUseMojaveTabBarStyle() +shouldUseMojaveTabBarStyle(void) { #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14 if (@available(macos 10.14, *)) { @@ -360,7 +360,7 @@ - (NSInteger)tag /// Returns the pasteboard type to use for retrieving file names from a list of /// files. /// @return The pasteboard type that can be passed to NSPasteboard for registration. -NSPasteboardType getPasteboardFilenamesType() +NSPasteboardType getPasteboardFilenamesType(void) { #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13 return NSPasteboardTypeFileURL; @@ -407,3 +407,29 @@ NSPasteboardType getPasteboardFilenamesType() #endif } +/// Compare two version strings (must be in integers separated by dots) and see +/// if one is larger. +/// +/// @return 1 if newVersion is newer, 0 if equal, -1 if oldVersion newer. +int compareSemanticVersions(NSString *oldVersion, NSString *newVersion) +{ + NSArray *oldVersionItems = [oldVersion componentsSeparatedByString:@"."]; + NSArray *newVersionItems = [newVersion componentsSeparatedByString:@"."]; + // Compare two arrays lexographically. We just assume that version + // numbers are also X.Y.Z… with no "beta" etc texts. + for (int i = 0; i < oldVersionItems.count || i < newVersionItems.count; i++) { + if (i >= newVersionItems.count) { + return -1; + } + if (i >= oldVersionItems.count) { + return 1; + } + if (newVersionItems[i].integerValue > oldVersionItems[i].integerValue) { + return 1; + } + else if (newVersionItems[i].integerValue < oldVersionItems[i].integerValue) { + return -1; + } + } + return 0; +} diff --git a/src/Makefile b/src/Makefile index 639d3da220..5dd5377dd3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -3666,6 +3666,9 @@ $(RUNTIME_FOLDER_LIST): macvim: $(VIMTARGET) $(RUNTIME_FOLDER_LIST) xcodebuild -project MacVim/MacVim.xcodeproj $(XCODEFLAGS) +macvim-tests: + xcodebuild test -project MacVim/MacVim.xcodeproj -scheme MacVim $(XCODEFLAGS) + macvim-signed: MacVim/scripts/sign-developer-id $(RELEASEDIR)/MacVim.app $(ENTITLEMENTS)