Bug 473091 - "Saved Search doesnt allow "Tags isn't empty"" [r=kent sr=bienvenu]
authorKent James <kent@caspia.com>
Tue, 10 Mar 2009 09:24:42 +0000
changeset 2170 c55d44c0a936ff3c39a32ce39e8af375961ec1f6
parent 2169 b2161ed4236af25bbeb62f0f4df3ac6c9869d74f
child 2171 052188a962d523f3ce8d718f63597df2a4bc983d
push id1756
push userbugzilla@standard8.plus.com
push dateTue, 10 Mar 2009 09:30:39 +0000
treeherdercomm-central@c55d44c0a936 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskent, bienvenu
bugs473091
Bug 473091 - "Saved Search doesnt allow "Tags isn't empty"" [r=kent sr=bienvenu]
mail/base/content/mailWidgets.xml
mail/locales/en-US/chrome/messenger/search-operators.properties
mailnews/base/search/public/nsMsgSearchCore.idl
mailnews/base/search/src/nsMsgImapSearch.cpp
mailnews/base/search/src/nsMsgSearchTerm.cpp
mailnews/base/test/unit/test_searchTag.js
suite/locales/en-US/chrome/mailnews/search-operators.properties
suite/mailnews/mailWidgets.xml
--- a/mail/base/content/mailWidgets.xml
+++ b/mail/base/content/mailWidgets.xml
@@ -1105,17 +1105,18 @@
       <property name="opParentValue" onget="return this.internalOperator;">
         <setter>
           <![CDATA[
             // noop if we're not changing it
             if (this.internalOperator == val) return val;
 
             // Keywords has the null field IsEmpty
             if (this.searchAttribute == Components.interfaces.nsMsgSearchAttrib.Keywords) {
-              if (val == Components.interfaces.nsMsgSearchOp.IsEmpty)
+              if (val == Components.interfaces.nsMsgSearchOp.IsEmpty ||
+                  val == Components.interfaces.nsMsgSearchOp.IsntEmpty)
                 this.setAttribute("selectedIndex", "-1");
               else
                 this.setAttribute("selectedIndex", "5");
             }
 
               // if it's not sender, to, cc, alladdresses, or toorcc, we don't care
               if (this.searchAttribute != Components.interfaces.nsMsgSearchAttrib.Sender && 
                 this.searchAttribute != Components.interfaces.nsMsgSearchAttrib.To && 
--- a/mail/locales/en-US/chrome/messenger/search-operators.properties
+++ b/mail/locales/en-US/chrome/messenger/search-operators.properties
@@ -17,9 +17,10 @@ 11=sounds like
 12=LdapDwim
 
 13=is greater than
 14=is less than
 
 15=NameCompletion
 16=is in my address book
 17=isn't in my address book
+18=isn't empty
 
--- a/mailnews/base/search/public/nsMsgSearchCore.idl
+++ b/mailnews/base/search/public/nsMsgSearchCore.idl
@@ -145,17 +145,18 @@ interface nsMsgSearchOp {
     const nsMsgSearchOpValue LdapDwim = 12; /* Do What I Mean for simple search */
 
     const nsMsgSearchOpValue IsGreaterThan = 13;
     const nsMsgSearchOpValue IsLessThan = 14;
 
     const nsMsgSearchOpValue NameCompletion = 15; /* Name Completion operator...as the name implies =) */
     const nsMsgSearchOpValue IsInAB = 16;
     const nsMsgSearchOpValue IsntInAB = 17;
-    const nsMsgSearchOpValue kNumMsgSearchOperators  = 18;     /* must be last operator */
+    const nsMsgSearchOpValue IsntEmpty = 18; /* primarily for tags */
+    const nsMsgSearchOpValue kNumMsgSearchOperators  = 19;     /* must be last operator */
 };
 
 typedef long nsMsgSearchWidgetValue;
 
 /* FEs use this to help build the search dialog box */
 [scriptable,uuid(903dd2e8-304e-11d3-92e6-00a0c900d445)]
 interface nsMsgSearchWidget {
     const nsMsgSearchWidgetValue Text = 0;
--- a/mailnews/base/search/src/nsMsgImapSearch.cpp
+++ b/mailnews/base/search/src/nsMsgImapSearch.cpp
@@ -351,16 +351,18 @@ nsMsgSearchValidityManager::InitOfflineM
   m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
   m_offlineMailTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
   m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
   m_offlineMailTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
   m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
   m_offlineMailTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
   m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
   m_offlineMailTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+  m_offlineMailTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+  m_offlineMailTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
   return rv;
 }
 
 
 nsresult 
 nsMsgSearchValidityManager::InitOnlineMailTable()
 {
   NS_ASSERTION(!m_onlineMailTable, "Online mail table already initted!");
@@ -570,16 +572,18 @@ nsMsgSearchValidityManager::InitOnlineMa
   m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
   m_onlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
   m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
   m_onlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
   m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
   m_onlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
   m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
   m_onlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+  m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+  m_onlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
 
   m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
   m_onlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
   m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
   m_onlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
   m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
   m_onlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::BeginsWith, 1);
   m_onlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::EndsWith, 1);
@@ -738,16 +742,18 @@ nsMsgSearchValidityManager::InitOfflineM
   m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
   m_offlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::DoesntContain, 1);
   m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
   m_offlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Is, 1);
   m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
   m_offlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::Isnt, 1);
   m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
   m_offlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsEmpty, 1);
+  m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
+  m_offlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::Keywords, nsMsgSearchOp::IsntEmpty, 1);
 
   m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
   m_offlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Contains, 1);
   m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
   m_offlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::DoesntContain, 1);
   m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
   m_offlineMailFilterTable->SetEnabled   (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Is, 1);
   m_offlineMailFilterTable->SetAvailable (nsMsgSearchAttrib::OtherHeader, nsMsgSearchOp::Isnt, 1);
--- a/mailnews/base/search/src/nsMsgSearchTerm.cpp
+++ b/mailnews/base/search/src/nsMsgSearchTerm.cpp
@@ -228,16 +228,17 @@ typedef struct
 
 nsMsgSearchOperatorEntry SearchOperatorEntryTable[] =
 {
   {nsMsgSearchOp::Contains, "contains"},
   {nsMsgSearchOp::DoesntContain,"doesn't contain"},
   {nsMsgSearchOp::Is,"is"},
   {nsMsgSearchOp::Isnt,  "isn't"},
   {nsMsgSearchOp::IsEmpty, "is empty"},
+  {nsMsgSearchOp::IsntEmpty, "isn't empty"},
   {nsMsgSearchOp::IsBefore, "is before"},
   {nsMsgSearchOp::IsAfter, "is after"},
   {nsMsgSearchOp::IsHigherThan, "is higher than"},
   {nsMsgSearchOp::IsLowerThan, "is lower than"},
   {nsMsgSearchOp::BeginsWith, "begins with"},
   {nsMsgSearchOp::EndsWith, "ends with"},
   {nsMsgSearchOp::IsInAB, "is in ab"},
   {nsMsgSearchOp::IsntInAB, "isn't in ab"},
@@ -988,18 +989,18 @@ nsresult nsMsgSearchTerm::MatchString (c
 {
   NS_ENSURE_ARG_POINTER(pResult);
   PRBool result = PR_FALSE;
 
   nsresult err = NS_OK;
   nsAutoString utf16StrToMatch;
   nsAutoString needle;
 
-  // Save some performance for opIsEmpty
-  if(nsMsgSearchOp::IsEmpty != m_operator)
+  // Save some performance for opIsEmpty / opIsntEmpty
+  if(nsMsgSearchOp::IsEmpty != m_operator && nsMsgSearchOp::IsntEmpty != m_operator)
   {
     NS_ASSERTION(MsgIsUTF8(nsDependentCString(m_value.string)),
                  "m_value.string is not UTF-8");
     CopyUTF8toUTF16(nsDependentCString(m_value.string), needle);
 
     if (charset != nsnull)
     {
       ConvertToUnicode(charset, stringToMatch ? stringToMatch : "",
@@ -1046,16 +1047,21 @@ nsresult nsMsgSearchTerm::MatchString (c
 #endif
       result = PR_TRUE;
     break;
   case nsMsgSearchOp::IsEmpty:
     // For IsEmpty, we didn't copy stringToMatch to utf16StrToMatch.
     if (!PL_strlen(stringToMatch))
       result = PR_TRUE;
     break;
+  case nsMsgSearchOp::IsntEmpty:
+    // For IsntEmpty, we didn't copy stringToMatch to utf16StrToMatch.
+    if (PL_strlen(stringToMatch))
+      result = PR_TRUE;
+    break;
   case nsMsgSearchOp::BeginsWith:
 #ifdef MOZILLA_INTERNAL_API
     if (StringBeginsWith(utf16StrToMatch, needle,
                          nsCaseInsensitiveStringComparator()))
 #else
     if (StringBeginsWith(utf16StrToMatch, needle, CaseInsensitiveCompare))
 #endif
       result = PR_TRUE;
@@ -1426,33 +1432,34 @@ nsresult nsMsgSearchTerm::MatchStatus(PR
 
   *pResult = matches;
   return rv;
 }
 
 /*
  * MatchKeyword Logic table (*pResult: + is true, - is false)
  *
- *         # Valid Tokens IsEmpty Contains DoesntContain Is     Isnt
- *                0           +       -         +         -       +
- * Term found?                      N   Y     N   Y     N   Y   N   Y
- *                1           -     -   +     +   -     -   +   +   -
- *               >1           -     -   +     +   -     -   -   +   +
+ *         # Valid Tokens IsEmpty IsntEmpty Contains DoesntContain Is     Isnt
+ *                0           +         -      -         +         -       +
+ * Term found?                               N   Y     N   Y     N   Y   N   Y
+ *                1           -         +    -   +     +   -     -   +   +   -
+ *               >1           -         +    -   +     +   -     -   -   +   +
  */
 // look up nsMsgSearchTerm::m_value in space-delimited keywordList
 nsresult nsMsgSearchTerm::MatchKeyword(const nsACString& keywordList, PRBool *pResult)
 {
   NS_ENSURE_ARG_POINTER(pResult);
   PRBool matches = PR_FALSE;
 
   // special-case empty for performance reasons
   if (keywordList.IsEmpty())
   {
     *pResult =  m_operator != nsMsgSearchOp::Contains &&
-                m_operator != nsMsgSearchOp::Is;
+                m_operator != nsMsgSearchOp::Is &&
+                m_operator != nsMsgSearchOp::IsntEmpty;
     return NS_OK;
   }
 
   // check if we can skip expensive valid keywordList test
   if (m_operator == nsMsgSearchOp::DoesntContain ||
       m_operator == nsMsgSearchOp::Contains)
   {
     nsCString keywordString(keywordList);
@@ -1498,16 +1505,23 @@ nsresult nsMsgSearchTerm::MatchKeyword(c
     {
       // IsEmpty fails on any valid token
       if (m_operator == nsMsgSearchOp::IsEmpty)
       {
         *pResult = PR_FALSE;
         return rv;
       }
 
+      // IsntEmpty succeeds on any valid token
+      if (m_operator == nsMsgSearchOp::IsntEmpty)
+      {
+        *pResult = PR_TRUE;
+        return rv;
+      }
+
       // Does this valid tag key match our search term?
       matches = keywordArray[i].Equals(m_value.string);
 
       // Is or Isn't partly determined on a single unmatched token
       if (!matches)
       {
         if (m_operator == nsMsgSearchOp::Is)
         {
@@ -1536,16 +1550,23 @@ nsresult nsMsgSearchTerm::MatchKeyword(c
   }
 
   if (m_operator == nsMsgSearchOp::IsEmpty)
   {
     *pResult = PR_TRUE;
     return NS_OK;
   }
 
+  if (m_operator == nsMsgSearchOp::IsntEmpty)
+  {
+    *pResult = PR_FALSE;
+    return NS_OK;
+  }
+
+
   // no valid match operator found
   NS_ERROR("invalid compare op for msg status");
   return NS_ERROR_FAILURE;
 }
 
 nsresult
 nsMsgSearchTerm::MatchPriority (nsMsgPriorityValue priorityToMatch,
                                 PRBool *pResult)
--- a/mailnews/base/test/unit/test_searchTag.js
+++ b/mailnews/base/test/unit/test_searchTag.js
@@ -50,16 +50,17 @@ const copyService = Cc["@mozilla.org/mes
 
 const nsMsgSearchScope = Ci.nsMsgSearchScope;
 const nsMsgSearchAttrib = Ci.nsMsgSearchAttrib;
 const nsMsgSearchOp = Ci.nsMsgSearchOp;
 
 const Isnt = nsMsgSearchOp.Isnt;
 const Is = nsMsgSearchOp.Is;
 const IsEmpty = nsMsgSearchOp.IsEmpty;
+const IsntEmpty = nsMsgSearchOp.IsntEmpty;
 const Contains = nsMsgSearchOp.Contains;
 const DoesntContain = nsMsgSearchOp.DoesntContain;
 const IsBefore = nsMsgSearchOp.IsBefore; // control entry not enabled
 
 const offlineMail = nsMsgSearchScope.offlineMail;
 const onlineMail = nsMsgSearchScope.onlineMail;
 const offlineMailFilter = nsMsgSearchScope.offlineMailFilter;
 const onlineMailFilter = nsMsgSearchScope.onlineMailFilter;
@@ -95,16 +96,20 @@ var Tests =
   { msgTag: Tag1,
     testTag: Tag1,
     op: DoesntContain,
     count: 0 },
   { msgTag: Tag1,
     testTag: Tag1,
     op: IsEmpty,
     count: 0 },
+  { msgTag: Tag1,
+    testTag: Tag1,
+    op: IsntEmpty,
+    count: 1 },
   //test an invalid tag, should act like empty
   { msgTag: Tag2,
     testTag: Tag1,
     op: Contains,
     count: 0 },
   { msgTag: Tag2,
     testTag: Tag1,
     op: DoesntContain,
@@ -116,16 +121,20 @@ var Tests =
   { msgTag: Tag2,
     testTag: Tag1,
     op: Isnt,
     count: 1 },
   { msgTag: Tag2,
     testTag: Tag1,
     op: IsEmpty,
     count: 1 },
+  { msgTag: Tag2,
+    testTag: Tag1,
+    op: IsntEmpty,
+    count: 0 },
 //   Message has two valid tags
   // test first tag
   { msgTag: Tag1Tag4,
     testTag: Tag1,
     op: Is,
     count: 0 },
   { msgTag: Tag1Tag4,
     testTag: Tag1,
@@ -138,16 +147,20 @@ var Tests =
   { msgTag: Tag1Tag4,
     testTag: Tag1,
     op: DoesntContain,
     count: 0 },
   { msgTag: Tag1Tag4,
     testTag: Tag1,
     op: IsEmpty,
     count: 0 },
+  { msgTag: Tag1Tag4,
+    testTag: Tag1,
+    op: IsntEmpty,
+    count: 1 },
   // test second tag
   { msgTag: Tag1Tag4,
     testTag: Tag4,
     op: Is,
     count: 0 },
   { msgTag: Tag1Tag4,
     testTag: Tag4,
     op: Isnt,
@@ -159,16 +172,20 @@ var Tests =
   { msgTag: Tag1Tag4,
     testTag: Tag4,
     op: DoesntContain,
     count: 0 },
   { msgTag: Tag1Tag4,
     testTag: Tag4,
     op: IsEmpty,
     count: 0 },
+  { msgTag: Tag1Tag4,
+    testTag: Tag4,
+    op: IsntEmpty,
+    count: 1 },
   // test tag not in message
   { msgTag: Tag1Tag4,
     testTag: Tag2,
     op: Is,
     count: 0 },
   { msgTag: Tag1Tag4,
     testTag: Tag2,
     op: Isnt,
@@ -180,16 +197,20 @@ var Tests =
   { msgTag: Tag1Tag4,
     testTag: Tag2,
     op: DoesntContain,
     count: 1 },
   { msgTag: Tag1Tag4,
     testTag: Tag2,
     op: IsEmpty,
     count: 0 },
+  { msgTag: Tag1Tag4,
+    testTag: Tag2,
+    op: IsntEmpty,
+    count: 1 },
   // empty message
   { msgTag: "",
     testTag: Tag2,
     op: Is,
     count: 0 },
   { msgTag: "",
     testTag: Tag2,
     op: Isnt,
@@ -201,16 +222,20 @@ var Tests =
   { msgTag: "",
     testTag: Tag2,
     op: DoesntContain,
     count: 1 },
   { msgTag: "",
     testTag: Tag2,
     op: IsEmpty,
     count: 1 },
+  { msgTag: "",
+    testTag: Tag2,
+    op: IsntEmpty,
+    count: 0 },
 // message with two tags, only one is valid
   // test with the single valid tag  
   { msgTag: Tag1Tag3,
     testTag: Tag1,
     op: Is,
     count: 1 },
   { msgTag: Tag1Tag3,
     testTag: Tag1,
@@ -223,16 +248,20 @@ var Tests =
   { msgTag: Tag1Tag3,
     testTag: Tag1,
     op: DoesntContain,
     count: 0 },
   { msgTag: Tag1Tag3,
     testTag: Tag1,
     op: IsEmpty,
     count: 0 },
+  { msgTag: Tag1Tag3,
+    testTag: Tag1,
+    op: IsntEmpty,
+    count: 1 },
   // test with a tag not in the message  
   { msgTag: Tag1Tag3,
     testTag: Tag2,
     op: Is,
     count: 0 },
   { msgTag: Tag1Tag3,
     testTag: Tag2,
     op: Isnt,
@@ -244,16 +273,20 @@ var Tests =
   { msgTag: Tag1Tag3,
     testTag: Tag2,
     op: DoesntContain,
     count: 1 },
   { msgTag: Tag1Tag3,
     testTag: Tag2,
     op: IsEmpty,
     count: 0 },
+  { msgTag: Tag1Tag3,
+    testTag: Tag2,
+    op: IsntEmpty,
+    count: 1 },
 //   Message has a duplicated tag
   // test the tag
   { msgTag: Tag1Tag1,
     testTag: Tag1,
     op: Is,
     count: 1 },
   { msgTag: Tag1Tag1,
     testTag: Tag1,
@@ -266,16 +299,20 @@ var Tests =
   { msgTag: Tag1Tag1,
     testTag: Tag1,
     op: DoesntContain,
     count: 0 },
   { msgTag: Tag1Tag1,
     testTag: Tag1,
     op: IsEmpty,
     count: 0 },
+  { msgTag: Tag1Tag1,
+    testTag: Tag1,
+    op: IsntEmpty,
+    count: 1 },
 
 ];
 
 var hdr;
 
 function run_test()
 {
   loadLocalMailAccount();
@@ -283,48 +320,53 @@ function run_test()
   // test that validity table terms are valid
 
   // offline mail table
   testValidityTable(offlineMail, Contains, Keywords, true);
   testValidityTable(offlineMail, DoesntContain, Keywords, true);
   testValidityTable(offlineMail, Is, Keywords, true);
   testValidityTable(offlineMail, Isnt, Keywords, true);
   testValidityTable(offlineMail, IsEmpty, Keywords, true);
+  testValidityTable(offlineMail, IsntEmpty, Keywords, true);
   testValidityTable(offlineMail, IsBefore, Keywords, false);
 
   // offline mail filter table
   testValidityTable(offlineMailFilter, Contains, Keywords, true);
   testValidityTable(offlineMailFilter, DoesntContain, Keywords, true);
   testValidityTable(offlineMailFilter, Is, Keywords, true);
   testValidityTable(offlineMailFilter, Isnt, Keywords, true);
   testValidityTable(offlineMailFilter, IsEmpty, Keywords, true);
+  testValidityTable(offlineMailFilter, IsntEmpty, Keywords, true);
   testValidityTable(offlineMailFilter, IsBefore, Keywords, false);
 
   // online mail
   testValidityTable(onlineMail, Contains, Keywords, true);
   testValidityTable(onlineMail, DoesntContain, Keywords, true);
   testValidityTable(onlineMail, Is, Keywords, false);
   testValidityTable(onlineMail, Isnt, Keywords, false);
   testValidityTable(onlineMail, IsEmpty, Keywords, false);
+  testValidityTable(onlineMail, IsntEmpty, Keywords, false);
   testValidityTable(onlineMail, IsBefore, Keywords, false);
 
   // online mail filter  
   testValidityTable(onlineMailFilter, Contains, Keywords, true);
   testValidityTable(onlineMailFilter, DoesntContain, Keywords, true);
   testValidityTable(onlineMailFilter, Is, Keywords, true);
   testValidityTable(onlineMailFilter, Isnt, Keywords, true);
   testValidityTable(onlineMailFilter, IsEmpty, Keywords, true);
+  testValidityTable(onlineMailFilter, IsntEmpty, Keywords, true);
   testValidityTable(onlineMailFilter, IsBefore, Keywords, false);
 
   // news
   testValidityTable(news, Contains, Keywords, false);
   testValidityTable(news, DoesntContain, Keywords, false);
   testValidityTable(news, Is, Keywords, false);
   testValidityTable(news, Isnt, Keywords, false);
   testValidityTable(news, IsEmpty, Keywords, false);
+  testValidityTable(news, IsntEmpty, Keywords, false);
   testValidityTable(news, IsBefore, Keywords, false);
 
   // delete any existing tags
   var tagArray = tagService.getAllTags({});
   for (var i = 0; i < tagArray.length; i++)
     tagService.deleteKey(tagArray[i].key);
 
   // add as valid tags Tag1 and Tag4
--- a/suite/locales/en-US/chrome/mailnews/search-operators.properties
+++ b/suite/locales/en-US/chrome/mailnews/search-operators.properties
@@ -17,9 +17,9 @@ 11=sounds like
 12=LdapDwim
 
 13=is greater than
 14=is less than
 
 15=NameCompletion
 16=is in my address book
 17=isn't in my address book
-
+18=isn't empty
--- a/suite/mailnews/mailWidgets.xml
+++ b/suite/mailnews/mailWidgets.xml
@@ -1080,17 +1080,18 @@
       <property name="opParentValue" onget="return this.internalOperator;">
         <setter>
           <![CDATA[
             // noop if we're not changing it
             if (this.internalOperator == val) return val;
 
             // Keywords has the null field IsEmpty
             if (this.searchAttribute == Components.interfaces.nsMsgSearchAttrib.Keywords) {
-              if (val == Components.interfaces.nsMsgSearchOp.IsEmpty)
+              if (val == Components.interfaces.nsMsgSearchOp.IsEmpty ||
+                  val == Components.interfaces.nsMsgSearchOp.IsntEmpty)
                 this.setAttribute("selectedIndex", "-1");
               else
                 this.setAttribute("selectedIndex", "5");
             }
 
               // if it's not sender, to, cc, alladdresses, or toorcc, we don't care
               if (this.searchAttribute != Components.interfaces.nsMsgSearchAttrib.Sender && 
                 this.searchAttribute != Components.interfaces.nsMsgSearchAttrib.To &&